Merge branch 'develop'
|
@ -9,7 +9,7 @@
|
|||
|
|
||||
*/
|
||||
$config['version'] = '1.4.0'; // This must be changed manually.
|
||||
$config['release_label'] = 'Dev'; // Leave empty for no title or add Alpha, Beta etc ...
|
||||
$config['release_label'] = 'Beta.1'; // Leave empty for no title or add Alpha, Beta etc ...
|
||||
$config['debug'] = Config::DEBUG_MODE;
|
||||
|
||||
/*
|
||||
|
@ -302,7 +302,7 @@ $config['cache_path'] = __DIR__ . '/../../storage/cache/';
|
|||
| new release.
|
||||
|
|
||||
*/
|
||||
$config['cache_busting_token'] = '93GX4';
|
||||
$config['cache_busting_token'] = '824HX';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -38,6 +38,8 @@ use EA\Engine\Types\Url;
|
|||
* @property Customers_Model $customers_model
|
||||
* @property Settings_Model $settings_model
|
||||
* @property Timezones $timezones
|
||||
* @property Synchronization $synchronization
|
||||
* @property Notifications $notifications
|
||||
* @property Roles_Model $roles_model
|
||||
* @property Secretaries_Model $secretaries_model
|
||||
* @property Admins_Model $admins_model
|
||||
|
@ -102,7 +104,7 @@ class Backend_api extends CI_Controller {
|
|||
'start_datetime >=' => $startDate,
|
||||
'end_datetime <=' => $endDate
|
||||
]),
|
||||
'unavailabilities' => $this->appointments_model->get_batch([
|
||||
'unavailability_events' => $this->appointments_model->get_batch([
|
||||
'is_unavailable' => TRUE,
|
||||
'start_datetime >=' => $startDate,
|
||||
'end_datetime <=' => $endDate
|
||||
|
@ -130,11 +132,11 @@ class Backend_api extends CI_Controller {
|
|||
}
|
||||
}
|
||||
|
||||
foreach ($response['unavailabilities'] as $index => $unavailability)
|
||||
foreach ($response['unavailability_events'] as $index => $unavailability_event)
|
||||
{
|
||||
if ((int)$unavailability['id_users_provider'] !== (int)$userId)
|
||||
if ((int)$unavailability_event['id_users_provider'] !== (int)$userId)
|
||||
{
|
||||
unset($response['unavailabilities'][$index]);
|
||||
unset($response['unavailability_events'][$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -152,11 +154,11 @@ class Backend_api extends CI_Controller {
|
|||
}
|
||||
}
|
||||
|
||||
foreach ($response['unavailabilities'] as $index => $unavailability)
|
||||
foreach ($response['unavailability_events'] as $index => $unavailability_event)
|
||||
{
|
||||
if ( ! in_array((int)$unavailability['id_users_provider'], $providers))
|
||||
if ( ! in_array((int)$unavailability_event['id_users_provider'], $providers))
|
||||
{
|
||||
unset($response['unavailabilities'][$index]);
|
||||
unset($response['unavailability_events'][$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -283,6 +285,8 @@ class Backend_api extends CI_Controller {
|
|||
$this->load->model('customers_model');
|
||||
$this->load->model('settings_model');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('notifications');
|
||||
$this->load->model('user_model');
|
||||
|
||||
// Save customer changes to the database.
|
||||
|
@ -290,10 +294,10 @@ class Backend_api extends CI_Controller {
|
|||
{
|
||||
$customer = json_decode($this->input->post('customer_data'), TRUE);
|
||||
|
||||
$required_privilegesileges = ( ! isset($customer['id']))
|
||||
$required_privileges = ( ! isset($customer['id']))
|
||||
? $this->privileges[PRIV_CUSTOMERS]['add']
|
||||
: $this->privileges[PRIV_CUSTOMERS]['edit'];
|
||||
if ($required_privilegesileges == FALSE)
|
||||
if ($required_privileges == FALSE)
|
||||
{
|
||||
throw new Exception('You do not have the required privileges for this task.');
|
||||
}
|
||||
|
@ -306,17 +310,17 @@ class Backend_api extends CI_Controller {
|
|||
{
|
||||
$appointment = json_decode($this->input->post('appointment_data'), TRUE);
|
||||
|
||||
$required_privilegesileges = ( ! isset($appointment['id']))
|
||||
$required_privileges = ( ! isset($appointment['id']))
|
||||
? $this->privileges[PRIV_APPOINTMENTS]['add']
|
||||
: $this->privileges[PRIV_APPOINTMENTS]['edit'];
|
||||
if ($required_privilegesileges == FALSE)
|
||||
if ($required_privileges == FALSE)
|
||||
{
|
||||
throw new Exception('You do not have the required privileges for this task.');
|
||||
}
|
||||
|
||||
$manage_mode = isset($appointment['id']);
|
||||
// If the appointment does not contain the customer record id, then it
|
||||
// means that is is going to be inserted. Get the customer's record id.
|
||||
// If the appointment does not contain the customer record id, then it means that is is going to be
|
||||
// inserted. Get the customer's record ID.
|
||||
if ( ! isset($appointment['id_users_customer']))
|
||||
{
|
||||
$appointment['id_users_customer'] = $customer['id'];
|
||||
|
@ -348,139 +352,10 @@ class Backend_api extends CI_Controller {
|
|||
'time_format' => $this->settings_model->get_setting('time_format')
|
||||
];
|
||||
|
||||
// Sync appointment changes with Google Calendar.
|
||||
try
|
||||
{
|
||||
$google_sync = $this->providers_model->get_setting('google_sync',
|
||||
$appointment['id_users_provider']);
|
||||
$this->synchronization->sync_appointment_deleted($appointment, $provider);
|
||||
$this->notifications->notify_appointment_deleted($appointment, $service, $provider, $customer, $settings);
|
||||
|
||||
if ($google_sync == TRUE)
|
||||
{
|
||||
$google_token = json_decode($this->providers_model->get_setting('google_token',
|
||||
$appointment['id_users_provider']));
|
||||
|
||||
$this->load->library('Google_sync');
|
||||
$this->google_sync->refresh_token($google_token->refresh_token);
|
||||
|
||||
if ($appointment['id_google_calendar'] == NULL)
|
||||
{
|
||||
$google_event = $this->google_sync->add_appointment($appointment, $provider,
|
||||
$service, $customer, $settings);
|
||||
$appointment['id_google_calendar'] = $google_event->id;
|
||||
$this->appointments_model->add($appointment); // Store google calendar id.
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->google_sync->update_appointment($appointment, $provider,
|
||||
$service, $customer, $settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$warnings[] = [
|
||||
'message' => $exception->getMessage(),
|
||||
'trace' => config('debug') ? $exception->getTrace() : []
|
||||
];
|
||||
}
|
||||
|
||||
// Send email notifications to provider and customer.
|
||||
try
|
||||
{
|
||||
$this->config->load('email');
|
||||
$email = new EmailClient($this, $this->config->config);
|
||||
|
||||
$send_provider = $this->providers_model
|
||||
->get_setting('notifications', $provider['id']);
|
||||
|
||||
if ( ! $manage_mode)
|
||||
{
|
||||
$customer_title = new Text(lang('appointment_booked'));
|
||||
$customer_message = new Text(lang('thank_you_for_appointment'));
|
||||
$provider_title = new Text(lang('appointment_added_to_your_plan'));
|
||||
$provider_message = new Text(lang('appointment_link_description'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$customer_title = new Text(lang('appointment_details_changed'));
|
||||
$customer_message = new Text('');
|
||||
$provider_title = new Text(lang('appointment_changes_saved'));
|
||||
$provider_message = new Text('');
|
||||
}
|
||||
|
||||
$customer_link = new Url(site_url('appointments/index/' . $appointment['hash']));
|
||||
$provider_link = new Url(site_url('backend/index/' . $appointment['hash']));
|
||||
|
||||
$send_customer = $this->settings_model->get_setting('customer_notifications');
|
||||
|
||||
$this->load->library('ics_file');
|
||||
$ics_stream = $this->ics_file->get_stream($appointment, $service, $provider, $customer);
|
||||
|
||||
if ((bool)$send_customer === TRUE)
|
||||
{
|
||||
$email->sendAppointmentDetails($appointment, $provider,
|
||||
$service, $customer, $settings, $customer_title,
|
||||
$customer_message, $customer_link, new Email($customer['email']), new Text($ics_stream));
|
||||
}
|
||||
|
||||
if ($send_provider == TRUE)
|
||||
{
|
||||
$email->sendAppointmentDetails($appointment, $provider,
|
||||
$service, $customer, $settings, $provider_title,
|
||||
$provider_message, $provider_link, new Email($provider['email']), new Text($ics_stream));
|
||||
}
|
||||
|
||||
// Notify admins
|
||||
$admins = $this->admins_model->get_batch();
|
||||
|
||||
foreach($admins as $admin)
|
||||
{
|
||||
if (!$admin['settings']['notifications'] === '0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$email->sendAppointmentDetails($appointment, $provider,
|
||||
$service, $customer, $settings, $provider_title,
|
||||
$provider_message, $provider_link, new Email($admin['email']), new Text($ics_stream));
|
||||
}
|
||||
|
||||
// Notify secretaries
|
||||
$secretaries = $this->secretaries_model->get_batch();
|
||||
|
||||
foreach($secretaries as $secretary)
|
||||
{
|
||||
if (!$secretary['settings']['notifications'] === '0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($provider['id'], $secretary['providers']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$email->sendAppointmentDetails($appointment, $provider,
|
||||
$service, $customer, $settings, $provider_title,
|
||||
$provider_message, $provider_link, new Email($secretary['email']), new Text($ics_stream));
|
||||
}
|
||||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$warnings[] = [
|
||||
'message' => $exception->getMessage(),
|
||||
'trace' => config('debug') ? $exception->getTrace() : []
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($warnings))
|
||||
{
|
||||
$response = AJAX_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
$response = ['warnings' => $warnings];
|
||||
}
|
||||
$response = AJAX_SUCCESS;
|
||||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
|
@ -1032,10 +907,10 @@ class Backend_api extends CI_Controller {
|
|||
$this->load->model('customers_model');
|
||||
$customer = json_decode($this->input->post('customer'), TRUE);
|
||||
|
||||
$required_privilegesileges = ( ! isset($customer['id']))
|
||||
$required_privileges = ( ! isset($customer['id']))
|
||||
? $this->privileges[PRIV_CUSTOMERS]['add']
|
||||
: $this->privileges[PRIV_CUSTOMERS]['edit'];
|
||||
if ($required_privilegesileges == FALSE)
|
||||
if ($required_privileges == FALSE)
|
||||
{
|
||||
throw new Exception('You do not have the required privileges for this task.');
|
||||
}
|
||||
|
@ -1105,10 +980,10 @@ class Backend_api extends CI_Controller {
|
|||
$this->load->model('services_model');
|
||||
$service = json_decode($this->input->post('service'), TRUE);
|
||||
|
||||
$required_privilegesileges = ( ! isset($service['id']))
|
||||
$required_privileges = ( ! isset($service['id']))
|
||||
? $this->privileges[PRIV_SERVICES]['add']
|
||||
: $this->privileges[PRIV_SERVICES]['edit'];
|
||||
if ($required_privilegesileges == FALSE)
|
||||
if ($required_privileges == FALSE)
|
||||
{
|
||||
throw new Exception('You do not have the required privileges for this task.');
|
||||
}
|
||||
|
@ -1215,10 +1090,10 @@ class Backend_api extends CI_Controller {
|
|||
$this->load->model('services_model');
|
||||
$category = json_decode($this->input->post('category'), TRUE);
|
||||
|
||||
$required_privilegesileges = ( ! isset($category['id']))
|
||||
$required_privileges = ( ! isset($category['id']))
|
||||
? $this->privileges[PRIV_SERVICES]['add']
|
||||
: $this->privileges[PRIV_SERVICES]['edit'];
|
||||
if ($required_privilegesileges == FALSE)
|
||||
if ($required_privileges == FALSE)
|
||||
{
|
||||
throw new Exception('You do not have the required privileges for this task.');
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class API_V1_Controller extends CI_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@ class API_V1_Controller extends CI_Controller {
|
|||
*
|
||||
* @param Exception $exception Thrown exception to be outputted.
|
||||
*/
|
||||
protected function _handleException(Exception $exception)
|
||||
protected function handle_exception(Exception $exception)
|
||||
{
|
||||
$error = [
|
||||
'code' => $exception->getCode() ?: 500,
|
||||
|
@ -166,7 +166,7 @@ class API_V1_Controller extends CI_Controller {
|
|||
*
|
||||
* @throws \EA\Engine\Api\V1\Exception
|
||||
*/
|
||||
protected function _throwRecordNotFound()
|
||||
protected function throw_record_not_found()
|
||||
{
|
||||
throw new \EA\Engine\Api\V1\Exception('The requested record was not found!', 404, 'Not Found');
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ class Admins extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($admins) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$response = new Response($admins);
|
||||
|
@ -92,7 +92,7 @@ class Admins extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ class Admins extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +141,7 @@ class Admins extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($batch) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$request = new Request();
|
||||
|
@ -158,7 +158,7 @@ class Admins extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ class Admins extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ use EA\Engine\Types\NonEmptyText;
|
|||
* @property Customers_Model $customers_model
|
||||
* @property Settings_Model $settings_model
|
||||
* @property Timezones $timezones
|
||||
* @property Notifications $notifications
|
||||
* @property Synchronization $synchronization
|
||||
* @property Roles_Model $roles_model
|
||||
* @property Secretaries_Model $secretaries_model
|
||||
* @property Admins_Model $admins_model
|
||||
|
@ -59,6 +61,12 @@ class Appointments extends API_V1_Controller {
|
|||
{
|
||||
parent::__construct();
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('settings_model');
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('notifications');
|
||||
$this->parser = new \EA\Engine\Api\V1\Parsers\Appointments;
|
||||
}
|
||||
|
||||
|
@ -84,7 +92,7 @@ class Appointments extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($appointments) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$response = new Response($appointments);
|
||||
|
@ -100,7 +108,7 @@ class Appointments extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,8 +117,6 @@ class Appointments extends API_V1_Controller {
|
|||
*/
|
||||
public function post()
|
||||
{
|
||||
$this->load->model('services_model');
|
||||
|
||||
try
|
||||
{
|
||||
// Insert the appointment to the database.
|
||||
|
@ -138,6 +144,20 @@ class Appointments extends API_V1_Controller {
|
|||
|
||||
$id = $this->appointments_model->add($appointment);
|
||||
|
||||
$service = $this->services_model->get_row($appointment['id_services']);
|
||||
$provider = $this->providers_model->get_row($appointment['id_users_provider']);
|
||||
$customer = $this->customers_model->get_row($appointment['id_users_customer']);
|
||||
$settings = [
|
||||
'company_name' => $this->settings_model->get_setting('company_name'),
|
||||
'company_email' => $this->settings_model->get_setting('company_email'),
|
||||
'company_link' => $this->settings_model->get_setting('company_link'),
|
||||
'date_format' => $this->settings_model->get_setting('date_format'),
|
||||
'time_format' => $this->settings_model->get_setting('time_format')
|
||||
];
|
||||
|
||||
$this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings, FALSE);
|
||||
$this->notifications->notify_appointment_saved($appointment, $service, $provider, $customer, $settings, FALSE);
|
||||
|
||||
// Fetch the new object from the database and return it to the client.
|
||||
$batch = $this->appointments_model->get_batch('id = ' . $id);
|
||||
$response = new Response($batch);
|
||||
|
@ -146,7 +166,7 @@ class Appointments extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,15 +184,30 @@ class Appointments extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($batch) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$request = new Request();
|
||||
$updatedAppointment = $request->getBody();
|
||||
$baseAppointment = $batch[0];
|
||||
$this->parser->decode($updatedAppointment, $baseAppointment);
|
||||
$updatedAppointment['id'] = $id;
|
||||
$id = $this->appointments_model->add($updatedAppointment);
|
||||
$updated_appointment = $request->getBody();
|
||||
$base_appointment = $batch[0];
|
||||
$this->parser->decode($updated_appointment, $base_appointment);
|
||||
$updated_appointment['id'] = $id;
|
||||
$id = $this->appointments_model->add($updated_appointment);
|
||||
|
||||
$service = $this->services_model->get_row($updated_appointment['id_services']);
|
||||
$provider = $this->providers_model->get_row($updated_appointment['id_users_provider']);
|
||||
$customer = $this->customers_model->get_row($updated_appointment['id_users_customer']);
|
||||
$settings = [
|
||||
'company_name' => $this->settings_model->get_setting('company_name'),
|
||||
'company_email' => $this->settings_model->get_setting('company_email'),
|
||||
'company_link' => $this->settings_model->get_setting('company_link'),
|
||||
'date_format' => $this->settings_model->get_setting('date_format'),
|
||||
'time_format' => $this->settings_model->get_setting('time_format')
|
||||
];
|
||||
|
||||
$this->synchronization->sync_appointment_saved($updated_appointment, $service, $provider, $customer, $settings, TRUE);
|
||||
$this->notifications->notify_appointment_saved($updated_appointment, $service, $provider, $customer, $settings, TRUE);
|
||||
|
||||
|
||||
// Fetch the updated object from the database and return it to the client.
|
||||
$batch = $this->appointments_model->get_batch('id = ' . $id);
|
||||
|
@ -181,7 +216,7 @@ class Appointments extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,8 +229,23 @@ class Appointments extends API_V1_Controller {
|
|||
{
|
||||
try
|
||||
{
|
||||
$appointment = $this->appointments_model->get_row($id);
|
||||
$service = $this->services_model->get_row($appointment['id_services']);
|
||||
$provider = $this->providers_model->get_row($appointment['id_users_provider']);
|
||||
$customer = $this->customers_model->get_row($appointment['id_users_customer']);
|
||||
$settings = [
|
||||
'company_name' => $this->settings_model->get_setting('company_name'),
|
||||
'company_email' => $this->settings_model->get_setting('company_email'),
|
||||
'company_link' => $this->settings_model->get_setting('company_link'),
|
||||
'date_format' => $this->settings_model->get_setting('date_format'),
|
||||
'time_format' => $this->settings_model->get_setting('time_format')
|
||||
];
|
||||
|
||||
$this->appointments_model->delete($id);
|
||||
|
||||
$this->synchronization->sync_appointment_deleted($appointment, $provider);
|
||||
$this->notifications->notify_appointment_deleted($appointment, $service, $provider, $customer, $settings);
|
||||
|
||||
$response = new Response([
|
||||
'code' => 200,
|
||||
'message' => 'Record was deleted successfully!'
|
||||
|
@ -205,7 +255,7 @@ class Appointments extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,8 +66,8 @@ class Availabilities extends API_V1_Controller {
|
|||
{
|
||||
try
|
||||
{
|
||||
$providerId = new UnsignedInteger($this->input->get('providerId'));
|
||||
$serviceId = new UnsignedInteger($this->input->get('serviceId'));
|
||||
$provider_id = new UnsignedInteger($this->input->get('providerId'));
|
||||
$service_id = new UnsignedInteger($this->input->get('serviceId'));
|
||||
|
||||
if ($this->input->get('date'))
|
||||
{
|
||||
|
@ -78,49 +78,49 @@ class Availabilities extends API_V1_Controller {
|
|||
$date = new DateTime();
|
||||
}
|
||||
|
||||
$provider = $this->providers_model->get_row($providerId->get());
|
||||
$service = $this->services_model->get_row($serviceId->get());
|
||||
$provider = $this->providers_model->get_row($provider_id->get());
|
||||
$service = $this->services_model->get_row($service_id->get());
|
||||
|
||||
$emptyPeriods = $this->_getProviderAvailableTimePeriods($providerId->get(),
|
||||
$empty_periods = $this->get_provider_available_time_periods($provider_id->get(),
|
||||
$date->format('Y-m-d'), []);
|
||||
|
||||
$availableHours = $this->_calculateAvailableHours($emptyPeriods,
|
||||
$available_hours = $this->calculate_available_hours($empty_periods,
|
||||
$date->format('Y-m-d'), $service['duration'], FALSE, $service['availabilities_type']);
|
||||
|
||||
if ($service['attendants_number'] > 1)
|
||||
{
|
||||
$availableHours = $this->_getMultipleAttendantsHours($date->format('Y-m-d'), $service, $provider);
|
||||
$available_hours = $this->get_multiple_attendants_hours($date->format('Y-m-d'), $service, $provider);
|
||||
}
|
||||
|
||||
// If the selected date is today, remove past hours. It is important include the timeout before
|
||||
// booking that is set in the back-office 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 the selected date is today, remove past hours. It is important include the timeout before booking
|
||||
// that is set in the back-office 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->format('Y-m-d') === date('Y-m-d'))
|
||||
{
|
||||
$bookAdvanceTimeout = $this->settings_model->get_setting('book_advance_timeout');
|
||||
$book_advance_timeout = $this->settings_model->get_setting('book_advance_timeout');
|
||||
|
||||
foreach ($availableHours as $index => $value)
|
||||
foreach ($available_hours as $index => $value)
|
||||
{
|
||||
$availableHour = strtotime($value);
|
||||
$currentHour = strtotime('+' . $bookAdvanceTimeout . ' minutes', strtotime('now'));
|
||||
if ($availableHour <= $currentHour)
|
||||
$available_hour = strtotime($value);
|
||||
$currentHour = strtotime('+' . $book_advance_timeout . ' minutes', strtotime('now'));
|
||||
if ($available_hour <= $currentHour)
|
||||
{
|
||||
unset($availableHours[$index]);
|
||||
unset($available_hours[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$availableHours = array_values($availableHours);
|
||||
sort($availableHours, SORT_STRING);
|
||||
$availableHours = array_values($availableHours);
|
||||
$available_hours = array_values($available_hours);
|
||||
sort($available_hours, SORT_STRING);
|
||||
$available_hours = array_values($available_hours);
|
||||
|
||||
$this->output
|
||||
->set_content_type('application/json')
|
||||
->set_output(json_encode($availableHours));
|
||||
->set_output(json_encode($available_hours));
|
||||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ class Availabilities extends API_V1_Controller {
|
|||
*
|
||||
* @return array Returns an array with the available time periods of the provider.
|
||||
*/
|
||||
protected function _getProviderAvailableTimePeriods(
|
||||
protected function get_provider_available_time_periods(
|
||||
$provider_id,
|
||||
$selected_date,
|
||||
$exclude_appointments = []
|
||||
|
@ -342,7 +342,7 @@ class Availabilities extends API_V1_Controller {
|
|||
*
|
||||
* @return array Returns an array with the available hours for the appointment.
|
||||
*/
|
||||
protected function _calculateAvailableHours(
|
||||
protected function calculate_available_hours(
|
||||
array $empty_periods,
|
||||
$selected_date,
|
||||
$service_duration,
|
||||
|
@ -385,7 +385,7 @@ class Availabilities extends API_V1_Controller {
|
|||
*
|
||||
* @return array Returns the available hours array.
|
||||
*/
|
||||
protected function _getMultipleAttendantsHours(
|
||||
protected function get_multiple_attendants_hours(
|
||||
$selected_date,
|
||||
$service,
|
||||
$provider
|
||||
|
@ -412,8 +412,8 @@ class Availabilities extends API_V1_Controller {
|
|||
]
|
||||
];
|
||||
|
||||
$periods = $this->_removeBreaks($selected_date, $periods, $working_hours['breaks']);
|
||||
$periods = $this->_removeUnavailabilities($periods, $unavailabilities);
|
||||
$periods = $this->remove_breaks($selected_date, $periods, $working_hours['breaks']);
|
||||
$periods = $this->remove_unavailabilities($periods, $unavailabilities);
|
||||
|
||||
$hours = [];
|
||||
|
||||
|
@ -455,7 +455,7 @@ class Availabilities extends API_V1_Controller {
|
|||
*
|
||||
* @return array Returns the available time periods without the breaks.
|
||||
*/
|
||||
public function _removeBreaks($selected_date, $periods, $breaks)
|
||||
public function remove_breaks($selected_date, $periods, $breaks)
|
||||
{
|
||||
if ( ! $breaks)
|
||||
{
|
||||
|
@ -517,7 +517,7 @@ class Availabilities extends API_V1_Controller {
|
|||
*
|
||||
* @return array Returns the available time periods without the unavailabilities.
|
||||
*/
|
||||
public function _removeUnavailabilities($periods, $unavailabilities)
|
||||
public function remove_unavailabilities($periods, $unavailabilities)
|
||||
{
|
||||
foreach ($unavailabilities as $unavailability)
|
||||
{
|
||||
|
|
|
@ -76,7 +76,7 @@ class Categories extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($categories) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$response = new Response($categories);
|
||||
|
@ -92,7 +92,7 @@ class Categories extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ class Categories extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,15 +141,15 @@ class Categories extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($batch) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$request = new Request();
|
||||
$updatedCategory = $request->getBody();
|
||||
$baseCategory = $batch[0];
|
||||
$this->parser->decode($updatedCategory, $baseCategory);
|
||||
$updatedCategory['id'] = $id;
|
||||
$id = $this->services_model->add_category($updatedCategory);
|
||||
$updated_category = $request->getBody();
|
||||
$base_category = $batch[0];
|
||||
$this->parser->decode($updated_category, $base_category);
|
||||
$updated_category['id'] = $id;
|
||||
$id = $this->services_model->add_category($updated_category);
|
||||
|
||||
// Fetch the updated object from the database and return it to the client.
|
||||
$batch = $this->services_model->get_all_categories('id = ' . $id);
|
||||
|
@ -158,7 +158,7 @@ class Categories extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ class Categories extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ class Customers extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($customers) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$response = new Response($customers);
|
||||
|
@ -92,7 +92,7 @@ class Customers extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ class Customers extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,15 +141,15 @@ class Customers extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($batch) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$request = new Request();
|
||||
$updatedCustomer = $request->getBody();
|
||||
$baseCustomer = $batch[0];
|
||||
$this->parser->decode($updatedCustomer, $baseCustomer);
|
||||
$updatedCustomer['id'] = $id;
|
||||
$id = $this->customers_model->add($updatedCustomer);
|
||||
$updated_customer = $request->getBody();
|
||||
$base_customer = $batch[0];
|
||||
$this->parser->decode($updated_customer, $base_customer);
|
||||
$updated_customer['id'] = $id;
|
||||
$id = $this->customers_model->add($updated_customer);
|
||||
|
||||
// Fetch the updated object from the database and return it to the client.
|
||||
$batch = $this->customers_model->get_batch('id = ' . $id);
|
||||
|
@ -158,7 +158,7 @@ class Customers extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ class Customers extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ class Providers extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($providers) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$response = new Response($providers);
|
||||
|
@ -92,7 +92,7 @@ class Providers extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ class Providers extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,15 +141,15 @@ class Providers extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($batch) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$request = new Request();
|
||||
$updatedProvider = $request->getBody();
|
||||
$baseProvider = $batch[0];
|
||||
$this->parser->decode($updatedProvider, $baseProvider);
|
||||
$updatedProvider['id'] = $id;
|
||||
$id = $this->providers_model->add($updatedProvider);
|
||||
$updated_provider = $request->getBody();
|
||||
$base_provider = $batch[0];
|
||||
$this->parser->decode($updated_provider, $base_provider);
|
||||
$updated_provider['id'] = $id;
|
||||
$id = $this->providers_model->add($updated_provider);
|
||||
|
||||
// Fetch the updated object from the database and return it to the client.
|
||||
$batch = $this->providers_model->get_batch('id = ' . $id);
|
||||
|
@ -158,7 +158,7 @@ class Providers extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ class Providers extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ class Secretaries extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($secretaries) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$response = new Response($secretaries);
|
||||
|
@ -92,7 +92,7 @@ class Secretaries extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ class Secretaries extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,15 +141,15 @@ class Secretaries extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($batch) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$request = new Request();
|
||||
$updatedSecretary = $request->getBody();
|
||||
$baseSecretary = $batch[0];
|
||||
$this->parser->decode($updatedSecretary, $baseSecretary);
|
||||
$updatedSecretary['id'] = $id;
|
||||
$id = $this->secretaries_model->add($updatedSecretary);
|
||||
$updated_secretary = $request->getBody();
|
||||
$base_secretary = $batch[0];
|
||||
$this->parser->decode($updated_secretary, $base_secretary);
|
||||
$updated_secretary['id'] = $id;
|
||||
$id = $this->secretaries_model->add($updated_secretary);
|
||||
|
||||
// Fetch the updated object from the database and return it to the client.
|
||||
$batch = $this->secretaries_model->get_batch('id = ' . $id);
|
||||
|
@ -158,7 +158,7 @@ class Secretaries extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ class Secretaries extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ class Services extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($services) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$response = new Response($services);
|
||||
|
@ -92,7 +92,7 @@ class Services extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ class Services extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,15 +141,15 @@ class Services extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($batch) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$request = new Request();
|
||||
$updatedService = $request->getBody();
|
||||
$baseService = $batch[0];
|
||||
$this->parser->decode($updatedService, $baseService);
|
||||
$updatedService['id'] = $id;
|
||||
$id = $this->services_model->add($updatedService);
|
||||
$updated_service = $request->getBody();
|
||||
$base_service = $batch[0];
|
||||
$this->parser->decode($updated_service, $base_service);
|
||||
$updated_service['id'] = $id;
|
||||
$id = $this->services_model->add($updated_service);
|
||||
|
||||
// Fetch the updated object from the database and return it to the client.
|
||||
$batch = $this->services_model->get_batch('id = ' . $id);
|
||||
|
@ -158,7 +158,7 @@ class Services extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ class Services extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$this->_handleException($exception);
|
||||
$this->handle_exception($exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ class Settings extends API_V1_Controller {
|
|||
|
||||
if (empty($setting))
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
unset($setting['id']);
|
||||
|
@ -110,7 +110,7 @@ class Settings extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,7 @@ class Settings extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,7 +162,7 @@ class Settings extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ class Unavailabilities extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($unavailabilities) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$response = new Response($unavailabilities);
|
||||
|
@ -92,7 +92,7 @@ class Unavailabilities extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ class Unavailabilities extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +141,7 @@ class Unavailabilities extends API_V1_Controller {
|
|||
|
||||
if ($id !== NULL && count($batch) === 0)
|
||||
{
|
||||
$this->_throwRecordNotFound();
|
||||
$this->throw_record_not_found();
|
||||
}
|
||||
|
||||
$request = new Request();
|
||||
|
@ -158,7 +158,7 @@ class Unavailabilities extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ class Unavailabilities extends API_V1_Controller {
|
|||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
exit($this->_handleException($exception));
|
||||
exit($this->handle_exception($exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
552
application/libraries/Availability.php
Normal file
|
@ -0,0 +1,552 @@
|
|||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Open Source Web Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) 2013 - 2020, Alex Tselegidis
|
||||
* @license http://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link http://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Class Availability
|
||||
*
|
||||
* Handles the availability generation of providers, based on their working plan and their schedule.
|
||||
*/
|
||||
class Availability {
|
||||
/**
|
||||
* @var CI_Controller
|
||||
*/
|
||||
protected $CI;
|
||||
|
||||
/**
|
||||
* Availability constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->CI =& get_instance();
|
||||
$this->CI->load->model('providers_model');
|
||||
$this->CI->load->model('secretaries_model');
|
||||
$this->CI->load->model('secretaries_model');
|
||||
$this->CI->load->model('admins_model');
|
||||
$this->CI->load->model('appointments_model');
|
||||
$this->CI->load->model('settings_model');
|
||||
$this->CI->load->library('ics_file');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available hours of a provider.
|
||||
*
|
||||
* @param string $date Selected date (Y-m-d).
|
||||
* @param array $service Service record.
|
||||
* @param array $provider Provider record.
|
||||
* @param int|null $exclude_appointment_id Exclude an appointment from the availability generation.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_available_hours($date, $service, $provider, $exclude_appointment_id = NULL)
|
||||
{
|
||||
$available_periods = $this->get_available_periods($date, $provider, $exclude_appointment_id);
|
||||
|
||||
$available_hours = $this->generate_available_hours($date, $service, $available_periods);
|
||||
|
||||
if ($service['attendants_number'] > 1)
|
||||
{
|
||||
$available_hours = $this->consider_multiple_attendants($date, $service, $provider);
|
||||
}
|
||||
|
||||
return $this->consider_book_advance_timeout($date, $available_hours, $provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array containing the free time periods (start - end) of a selected date.
|
||||
*
|
||||
* This method is very important because there are many cases where the system needs to know when a provider is
|
||||
* available for an appointment. This method will return an array that belongs to the selected date and contains
|
||||
* values that have the start and the end time of an available time period.
|
||||
*
|
||||
* @param string $date Select date string.
|
||||
* @param array $provider Provider record.
|
||||
* @param int|null $exclude_appointment_id Exclude an appointment from the availability generation.
|
||||
*
|
||||
* @return array Returns an array with the available time periods of the provider.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_available_periods(
|
||||
$date,
|
||||
$provider,
|
||||
$exclude_appointment_id = NULL
|
||||
)
|
||||
{
|
||||
// Get the service, provider's working plan and provider appointments.
|
||||
$working_plan = json_decode($provider['settings']['working_plan'], TRUE);
|
||||
|
||||
// Get the provider's working plan exceptions.
|
||||
$working_plan_exceptions = json_decode($provider['settings']['working_plan_exceptions'], TRUE);
|
||||
|
||||
$conditions = [
|
||||
'id_users_provider' => $provider['id'],
|
||||
];
|
||||
|
||||
// Sometimes it might be necessary to exclude an appointment from the calculation (e.g. when editing an
|
||||
// existing appointment).
|
||||
if ($exclude_appointment_id)
|
||||
{
|
||||
$conditions['id !='] = $exclude_appointment_id;
|
||||
}
|
||||
|
||||
$appointments = $this->CI->appointments_model->get_batch($conditions);
|
||||
|
||||
// Find the empty spaces on the plan. The first split between the plan is due to a break (if any). After that
|
||||
// every reserved appointment is considered to be a taken space in the plan.
|
||||
$date_working_plan = $working_plan[strtolower(date('l', strtotime($date)))];
|
||||
|
||||
// Search if the $date is an custom availability period added outside the normal working plan.
|
||||
if (isset($working_plan_exceptions[$date]))
|
||||
{
|
||||
$date_working_plan = $working_plan_exceptions[$date];
|
||||
}
|
||||
|
||||
$periods = [];
|
||||
|
||||
if (isset($date_working_plan['breaks']))
|
||||
{
|
||||
$periods[] = [
|
||||
'start' => $date_working_plan['start'],
|
||||
'end' => $date_working_plan['end']
|
||||
];
|
||||
|
||||
$day_start = new DateTime($date_working_plan['start']);
|
||||
$day_end = new DateTime($date_working_plan['end']);
|
||||
|
||||
// Split the working plan to available time periods that do not contain the breaks in them.
|
||||
foreach ($date_working_plan['breaks'] as $index => $break)
|
||||
{
|
||||
$break_start = new DateTime($break['start']);
|
||||
$break_end = new DateTime($break['end']);
|
||||
|
||||
if ($break_start < $day_start)
|
||||
{
|
||||
$break_start = $day_start;
|
||||
}
|
||||
|
||||
if ($break_end > $day_end)
|
||||
{
|
||||
$break_end = $day_end;
|
||||
}
|
||||
|
||||
if ($break_start >= $break_end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($periods as $key => $period)
|
||||
{
|
||||
$period_start = new DateTime($period['start']);
|
||||
$period_end = new DateTime($period['end']);
|
||||
|
||||
$remove_current_period = FALSE;
|
||||
|
||||
if ($break_start > $period_start && $break_start < $period_end && $break_end > $period_start)
|
||||
{
|
||||
$periods[] = [
|
||||
'start' => $period_start->format('H:i'),
|
||||
'end' => $break_start->format('H:i')
|
||||
];
|
||||
|
||||
$remove_current_period = TRUE;
|
||||
}
|
||||
|
||||
if ($break_start < $period_end && $break_end > $period_start && $break_end < $period_end)
|
||||
{
|
||||
$periods[] = [
|
||||
'start' => $break_end->format('H:i'),
|
||||
'end' => $period_end->format('H:i')
|
||||
];
|
||||
|
||||
$remove_current_period = TRUE;
|
||||
}
|
||||
|
||||
if ($break_start == $period_start && $break_end == $period_end)
|
||||
{
|
||||
$remove_current_period = TRUE;
|
||||
}
|
||||
|
||||
if ($remove_current_period)
|
||||
{
|
||||
unset($periods[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Break the empty periods with the reserved appointments.
|
||||
foreach ($appointments as $appointment)
|
||||
{
|
||||
foreach ($periods as $index => &$period)
|
||||
{
|
||||
$appointment_start = new DateTime($appointment['start_datetime']);
|
||||
$appointment_end = new DateTime($appointment['end_datetime']);
|
||||
|
||||
if ($appointment_start >= $appointment_end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$period_start = new DateTime($date . ' ' . $period['start']);
|
||||
$period_end = new DateTime($date . ' ' . $period['end']);
|
||||
|
||||
if ($appointment_start <= $period_start && $appointment_end <= $period_end && $appointment_end <= $period_start)
|
||||
{
|
||||
// The appointment does not belong in this time period, so we will not change anything.
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($appointment_start <= $period_start && $appointment_end <= $period_end && $appointment_end >= $period_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'] = $appointment_end->format('H:i');
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($appointment_start >= $period_start && $appointment_end < $period_end)
|
||||
{
|
||||
// The appointment is inside the time period, so we will split the period into two new
|
||||
// others.
|
||||
unset($periods[$index]);
|
||||
|
||||
$periods[] = [
|
||||
'start' => $period_start->format('H:i'),
|
||||
'end' => $appointment_start->format('H:i')
|
||||
];
|
||||
|
||||
$periods[] = [
|
||||
'start' => $appointment_end->format('H:i'),
|
||||
'end' => $period_end->format('H:i')
|
||||
];
|
||||
}
|
||||
else if ($appointment_start == $period_start && $appointment_end == $period_end)
|
||||
{
|
||||
unset($periods[$index]); // The whole period is blocked so remove it from the available periods array.
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($appointment_start >= $period_start && $appointment_end >= $period_start && $appointment_start <= $period_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'] = $appointment_start->format('H:i');
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($appointment_start >= $period_start && $appointment_end >= $period_end && $appointment_start >= $period_end)
|
||||
{
|
||||
// The appointment does not belong in the period so do not change anything.
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($appointment_start <= $period_start && $appointment_end >= $period_end && $appointment_start <= $period_end)
|
||||
{
|
||||
// The appointment is bigger than the period, so this period needs to be removed.
|
||||
unset($periods[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($periods);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the available 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 string $date Selected date (Y-m-d).
|
||||
* @param array $service Service record.
|
||||
* @param array $empty_periods Empty periods as generated by the "get_provider_available_time_periods"
|
||||
* method.
|
||||
*
|
||||
* @return array Returns an array with the available hours for the appointment.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function generate_available_hours(
|
||||
$date,
|
||||
$service,
|
||||
$empty_periods
|
||||
)
|
||||
{
|
||||
$available_hours = [];
|
||||
|
||||
foreach ($empty_periods as $period)
|
||||
{
|
||||
$start_hour = new DateTime($date . ' ' . $period['start']);
|
||||
$end_hour = new DateTime($date . ' ' . $period['end']);
|
||||
$interval = $service['availabilities_type'] === AVAILABILITIES_TYPE_FIXED ? (int)$service['duration'] : 15;
|
||||
|
||||
$current_hour = $start_hour;
|
||||
$diff = $current_hour->diff($end_hour);
|
||||
|
||||
while (($diff->h * 60 + $diff->i) >= (int)$service['duration'])
|
||||
{
|
||||
$available_hours[] = $current_hour->format('H:i');
|
||||
$current_hour->add(new DateInterval('PT' . $interval . 'M'));
|
||||
$diff = $current_hour->diff($end_hour);
|
||||
}
|
||||
}
|
||||
|
||||
return $available_hours;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple attendants hours.
|
||||
*
|
||||
* This method will add the additional appointment hours whenever a service accepts multiple attendants.
|
||||
*
|
||||
* @param string $date Selected date (Y-m-d).
|
||||
* @param array $service Service record.
|
||||
* @param array $provider Provider record.
|
||||
*
|
||||
* @return array Returns the available hours array.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function consider_multiple_attendants(
|
||||
$date,
|
||||
$service,
|
||||
$provider
|
||||
)
|
||||
{
|
||||
$unavailability_events = $this->CI->appointments_model->get_batch([
|
||||
'is_unavailable' => TRUE,
|
||||
'DATE(start_datetime)' => $date,
|
||||
'id_users_provider' => $provider['id']
|
||||
]);
|
||||
|
||||
$working_plan = json_decode($provider['settings']['working_plan'], TRUE);
|
||||
$working_day = strtolower(date('l', strtotime($date)));
|
||||
$working_hours = $working_plan[$working_day];
|
||||
|
||||
$periods = [
|
||||
[
|
||||
'start' => new DateTime($date . ' ' . $working_hours['start']),
|
||||
'end' => new DateTime($date . ' ' . $working_hours['end'])
|
||||
]
|
||||
];
|
||||
|
||||
$periods = $this->remove_breaks($date, $periods, $working_hours['breaks']);
|
||||
$periods = $this->remove_unavailability_events($periods, $unavailability_events);
|
||||
|
||||
$hours = [];
|
||||
|
||||
$interval_value = $service['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)
|
||||
{
|
||||
$slot_start = clone $period['start'];
|
||||
$slot_end = clone $slot_start;
|
||||
$slot_end->add($duration);
|
||||
|
||||
while ($slot_end <= $period['end'])
|
||||
{
|
||||
// Check reserved attendants for this time slot and see if current attendants fit.
|
||||
$appointment_attendants_number = $this->CI->appointments_model->get_attendants_number_for_period(
|
||||
$slot_start,
|
||||
$slot_end,
|
||||
$service['id']
|
||||
);
|
||||
|
||||
if ($appointment_attendants_number < $service['attendants_number'])
|
||||
{
|
||||
$hours[] = $slot_start->format('H:i');
|
||||
}
|
||||
|
||||
$slot_start->add($interval);
|
||||
$slot_end->add($interval);
|
||||
}
|
||||
}
|
||||
|
||||
return $hours;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove breaks from available time periods.
|
||||
*
|
||||
* @param string $selected_date Selected data (Y-m-d format).
|
||||
* @param array $periods Time periods of the current date.
|
||||
* @param array $breaks Breaks array for the current date.
|
||||
*
|
||||
* @return array Returns the available time periods without the breaks.
|
||||
* @throws Exception
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the unavailability entries from the available time periods of the selected date.
|
||||
*
|
||||
* @param array $periods Available time periods.
|
||||
* @param array $unavailability_events Unavailability events of the current date.
|
||||
*
|
||||
* @return array Returns the available time periods without the unavailability events.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function remove_unavailability_events($periods, $unavailability_events)
|
||||
{
|
||||
foreach ($unavailability_events as $unavailability_event)
|
||||
{
|
||||
$unavailability_start = new DateTime($unavailability_event['start_datetime']);
|
||||
$unavailability_end = new DateTime($unavailability_event['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)
|
||||
{
|
||||
// Unavailability contains period
|
||||
$period['start'] = $unavailability_end;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $periods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consider the book advance timeout and remove available hours that have passed the threshold.
|
||||
*
|
||||
* If the selected date is today, remove past hours. It is important include the timeout before booking
|
||||
* that is set in the back-office 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.
|
||||
*
|
||||
* @param string $selected_date The selected date.
|
||||
* @param array $available_hours Already generated available hours.
|
||||
* @param array $provider Provider information.
|
||||
*
|
||||
* @return array Returns the updated available hours.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function consider_book_advance_timeout($selected_date, $available_hours, $provider)
|
||||
{
|
||||
$provider_timezone = new DateTimeZone($provider['timezone']);
|
||||
|
||||
$book_advance_timeout = $this->CI->settings_model->get_setting('book_advance_timeout');
|
||||
|
||||
$threshold = new DateTime('+' . $book_advance_timeout . ' minutes', $provider_timezone);
|
||||
|
||||
foreach ($available_hours as $index => $value)
|
||||
{
|
||||
$available_hour = new DateTime($selected_date . ' ' . $value, $provider_timezone);
|
||||
|
||||
if ($available_hour->getTimestamp() <= $threshold->getTimestamp())
|
||||
{
|
||||
unset($available_hours[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
$available_hours = array_values($available_hours);
|
||||
sort($available_hours, SORT_STRING);
|
||||
return array_values($available_hours);
|
||||
}
|
||||
}
|
227
application/libraries/Notifications.php
Normal file
|
@ -0,0 +1,227 @@
|
|||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Open Source Web Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) 2013 - 2020, Alex Tselegidis
|
||||
* @license http://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link http://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
use EA\Engine\Notifications\Email as EmailClient;
|
||||
use EA\Engine\Types\Email;
|
||||
use EA\Engine\Types\Text;
|
||||
use EA\Engine\Types\Url;
|
||||
|
||||
/**
|
||||
* Class Notifications
|
||||
*
|
||||
* Handles the system notifications (mostly related to scheduling changes).
|
||||
*/
|
||||
class Notifications {
|
||||
/**
|
||||
* @var CI_Controller
|
||||
*/
|
||||
protected $CI;
|
||||
|
||||
/**
|
||||
* Notifications constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->CI =& get_instance();
|
||||
$this->CI->load->model('providers_model');
|
||||
$this->CI->load->model('secretaries_model');
|
||||
$this->CI->load->model('secretaries_model');
|
||||
$this->CI->load->model('admins_model');
|
||||
$this->CI->load->model('appointments_model');
|
||||
$this->CI->load->model('settings_model');
|
||||
$this->CI->load->library('ics_file');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the required notifications, related to an appointment creation/modification.
|
||||
*
|
||||
* @param array $appointment Appointment record.
|
||||
* @param array $service Service record.
|
||||
* @param array $provider Provider record.
|
||||
* @param array $customer Customer record.
|
||||
* @param array $settings Required settings for the notification content.
|
||||
* @param bool|false $manage_mode
|
||||
*/
|
||||
public function notify_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode = FALSE)
|
||||
{
|
||||
// Send email notifications to customer and provider.
|
||||
try
|
||||
{
|
||||
$this->CI->config->load('email');
|
||||
|
||||
$email = new EmailClient($this->CI, $this->CI->config->config);
|
||||
|
||||
if ($manage_mode === FALSE)
|
||||
{
|
||||
$customer_title = new Text(lang('appointment_booked'));
|
||||
$customer_message = new Text(lang('thank_you_for_appointment'));
|
||||
$provider_title = new Text(lang('appointment_added_to_your_plan'));
|
||||
$provider_message = new Text(lang('appointment_link_description'));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$customer_title = new Text(lang('appointment_changes_saved'));
|
||||
$customer_message = new Text('');
|
||||
$provider_title = new Text(lang('appointment_details_changed'));
|
||||
$provider_message = new Text('');
|
||||
}
|
||||
|
||||
$customer_link = new Url(site_url('appointments/index/' . $appointment['hash']));
|
||||
$provider_link = new Url(site_url('backend/index/' . $appointment['hash']));
|
||||
|
||||
$send_customer = filter_var(
|
||||
$this->CI->settings_model->get_setting('customer_notifications'),
|
||||
FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
|
||||
$ics_stream = $this->CI->ics_file->get_stream($appointment, $service, $provider, $customer);
|
||||
|
||||
if ($send_customer === TRUE)
|
||||
{
|
||||
$email->sendAppointmentDetails($appointment, $provider,
|
||||
$service, $customer, $settings, $customer_title,
|
||||
$customer_message, $customer_link, new Email($customer['email']), new Text($ics_stream));
|
||||
}
|
||||
|
||||
$send_provider = filter_var(
|
||||
$this->CI->providers_model->get_setting('notifications', $provider['id']),
|
||||
FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if ($send_provider === TRUE)
|
||||
{
|
||||
$email->sendAppointmentDetails($appointment, $provider,
|
||||
$service, $customer, $settings, $provider_title,
|
||||
$provider_message, $provider_link, new Email($provider['email']), new Text($ics_stream));
|
||||
}
|
||||
|
||||
// Notify admins
|
||||
$admins = $this->CI->admins_model->get_batch();
|
||||
|
||||
foreach ($admins as $admin)
|
||||
{
|
||||
if ( ! $admin['settings']['notifications'] === '0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$email->sendAppointmentDetails($appointment, $provider,
|
||||
$service, $customer, $settings, $provider_title,
|
||||
$provider_message, $provider_link, new Email($admin['email']), new Text($ics_stream));
|
||||
}
|
||||
|
||||
// Notify secretaries
|
||||
$secretaries = $this->CI->secretaries_model->get_batch();
|
||||
|
||||
foreach ($secretaries as $secretary)
|
||||
{
|
||||
if ( ! $secretary['settings']['notifications'] === '0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($provider['id'], $secretary['providers']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$email->sendAppointmentDetails($appointment, $provider,
|
||||
$service, $customer, $settings, $provider_title,
|
||||
$provider_message, $provider_link, new Email($secretary['email']), new Text($ics_stream));
|
||||
}
|
||||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
log_message('error', $exception->getMessage());
|
||||
log_message('error', $exception->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the required notifications, related to an appointment removal.
|
||||
*
|
||||
* @param array $appointment Appointment record.
|
||||
* @param array $service Service record.
|
||||
* @param array $provider Provider record.
|
||||
* @param array $customer Customer record.
|
||||
* @param array $settings Required settings for the notification content.
|
||||
*/
|
||||
public function notify_appointment_deleted($appointment, $service, $provider, $customer, $settings)
|
||||
{
|
||||
// Send email notification to customer and provider.
|
||||
try
|
||||
{
|
||||
$email = new EmailClient($this->CI, $this->CI->config->config);
|
||||
|
||||
$send_provider = filter_var($this->CI->providers_model->get_setting('notifications', $provider['id']),
|
||||
FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if ($send_provider === TRUE)
|
||||
{
|
||||
$email->sendDeleteAppointment($appointment, $provider,
|
||||
$service, $customer, $settings, new Email($provider['email']),
|
||||
new Text($this->CI->input->post('cancel_reason')));
|
||||
}
|
||||
|
||||
$send_customer = filter_var(
|
||||
$this->CI->settings_model->get_setting('customer_notifications'),
|
||||
FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if ($send_customer === TRUE)
|
||||
{
|
||||
$email->sendDeleteAppointment($appointment, $provider,
|
||||
$service, $customer, $settings, new Email($customer['email']),
|
||||
new Text($this->CI->input->post('cancel_reason')));
|
||||
}
|
||||
|
||||
// Notify admins
|
||||
$admins = $this->CI->admins_model->get_batch();
|
||||
|
||||
foreach ($admins as $admin)
|
||||
{
|
||||
if ( ! $admin['settings']['notifications'] === '0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$email->sendDeleteAppointment($appointment, $provider,
|
||||
$service, $customer, $settings, new Email($admin['email']),
|
||||
new Text($this->CI->input->post('cancel_reason')));
|
||||
}
|
||||
|
||||
// Notify secretaries
|
||||
$secretaries = $this->CI->secretaries_model->get_batch();
|
||||
|
||||
foreach ($secretaries as $secretary)
|
||||
{
|
||||
if ( ! $secretary['settings']['notifications'] === '0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($provider['id'], $secretary['providers']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$email->sendDeleteAppointment($appointment, $provider,
|
||||
$service, $customer, $settings, new Email($secretary['email']),
|
||||
new Text($this->CI->input->post('cancel_reason')));
|
||||
}
|
||||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$exceptions[] = $exception;
|
||||
}
|
||||
}
|
||||
}
|
120
application/libraries/Synchronization.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Open Source Web Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) 2013 - 2020, Alex Tselegidis
|
||||
* @license http://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link http://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Class Synchronization
|
||||
*
|
||||
* Handles the external calendar synchronization.
|
||||
*/
|
||||
class Synchronization {
|
||||
/**
|
||||
* @var CI_Controller
|
||||
*/
|
||||
protected $CI;
|
||||
|
||||
/**
|
||||
* Synchronization constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->CI =& get_instance();
|
||||
$this->CI->load->model('providers_model');
|
||||
$this->CI->load->model('appointments_model');
|
||||
$this->CI->load->library('google_sync');
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize changes made to the appointment with external calendars.
|
||||
*
|
||||
* @param array $appointment Appointment record.
|
||||
* @param array $service Service record.
|
||||
* @param array $provider Provider record.
|
||||
* @param array $customer Customer record.
|
||||
* @param array $settings Required settings for the notification content.
|
||||
* @param bool|false $manage_mode True if the appointment is being edited.
|
||||
*/
|
||||
public function sync_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode = FALSE)
|
||||
{
|
||||
try
|
||||
{
|
||||
$google_sync = filter_var(
|
||||
$this->CI->providers_model->get_setting('google_sync', $appointment['id_users_provider']),
|
||||
FILTER_VALIDATE_BOOLEAN
|
||||
);
|
||||
|
||||
if ($google_sync === TRUE)
|
||||
{
|
||||
$google_token = json_decode(
|
||||
$this->CI->providers_model->get_setting('google_token', $appointment['id_users_provider']));
|
||||
|
||||
$this->CI->load->library('google_sync');
|
||||
|
||||
$this->CI->google_sync->refresh_token($google_token->refresh_token);
|
||||
|
||||
if ($manage_mode === FALSE)
|
||||
{
|
||||
// Add appointment to Google Calendar.
|
||||
$google_event = $this->CI->google_sync->add_appointment($appointment, $provider,
|
||||
$service, $customer, $settings);
|
||||
$appointment['id_google_calendar'] = $google_event->id;
|
||||
$this->CI->appointments_model->add($appointment);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update appointment to Google Calendar.
|
||||
$appointment['id_google_calendar'] = $this->CI->appointments_model
|
||||
->get_value('id_google_calendar', $appointment['id']);
|
||||
|
||||
$this->CI->google_sync->update_appointment($appointment, $provider,
|
||||
$service, $customer, $settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
log_message('error', $exception->getMessage());
|
||||
log_message('error', $exception->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize removal of an appointment with external calendars.
|
||||
*
|
||||
* @param array $appointment Appointment record.
|
||||
* @param array $provider Provider record.
|
||||
*/
|
||||
public function sync_appointment_deleted($appointment, $provider)
|
||||
{
|
||||
if ($appointment['id_google_calendar'] != NULL)
|
||||
{
|
||||
try
|
||||
{
|
||||
$google_sync = filter_var(
|
||||
$this->CI->providers_model->get_setting('google_sync', $appointment['id_users_provider']),
|
||||
FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if ($google_sync === TRUE)
|
||||
{
|
||||
$google_token = json_decode($this->CI->providers_model->get_setting('google_token', $provider['id']));
|
||||
$this->CI->load->library('Google_sync');
|
||||
$this->CI->google_sync->refresh_token($google_token->refresh_token);
|
||||
$this->CI->google_sync->delete_appointment($provider, $appointment['id_google_calendar']);
|
||||
}
|
||||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
$exceptions[] = $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/bootstrap/css/bootstrap.min.css') ?>">
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/jquery-ui/jquery-ui.min.css') ?>">
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/jquery-qtip/jquery.qtip.min.css') ?>">
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/cookieconsent/cookieconsent.min.css') ?>">
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/css/frontend.css') ?>">
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/css/general.css') ?>">
|
||||
|
@ -32,17 +31,17 @@
|
|||
<span id="company-name"><?= $company_name ?></span>
|
||||
|
||||
<div id="steps">
|
||||
<div id="step-1" class="book-step active-step" title="<?= lang('service_and_provider') ?>">
|
||||
<div id="step-1" class="book-step active-step" data-tippy-content="<?= lang('service_and_provider') ?>">
|
||||
<strong>1</strong>
|
||||
</div>
|
||||
|
||||
<div id="step-2" class="book-step" title="<?= lang('appointment_date_and_time') ?>">
|
||||
<div id="step-2" class="book-step" data-toggle="tooltip" data-tippy-content="<?= lang('appointment_date_and_time') ?>">
|
||||
<strong>2</strong>
|
||||
</div>
|
||||
<div id="step-3" class="book-step" title="<?= lang('customer_information') ?>">
|
||||
<div id="step-3" class="book-step" data-toggle="tooltip" data-tippy-content="<?= lang('customer_information') ?>">
|
||||
<strong>3</strong>
|
||||
</div>
|
||||
<div id="step-4" class="book-step" title="<?= lang('appointment_confirmation') ?>">
|
||||
<div id="step-4" class="book-step" data-toggle="tooltip" data-tippy-content="<?= lang('appointment_confirmation') ?>">
|
||||
<strong>4</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -450,9 +449,10 @@
|
|||
<script src="<?= asset_url('assets/js/general_functions.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/jquery/jquery.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/jquery-ui/jquery-ui.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/jquery-qtip/jquery.qtip.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/cookieconsent/cookieconsent.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/bootstrap/js/bootstrap.bundle.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/popper/popper.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/tippy/tippy-bundle.umd.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/datejs/date.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/moment/moment.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/moment/moment-timezone-with-data.min.js') ?>"></script>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<script src="<?= asset_url('assets/js/backend_calendar_table_view.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/js/backend_calendar_google_sync.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/js/backend_calendar_appointments_modal.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/js/backend_calendar_unavailabilities_modal.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/js/backend_calendar_unavailability_events_modal.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/js/backend_calendar_api.js') ?>"></script>
|
||||
<script>
|
||||
var GlobalVariables = {
|
||||
|
@ -44,7 +44,7 @@
|
|||
<div id="calendar-filter" class="col-12 col-sm-5">
|
||||
<div class="form-group calendar-filter-items">
|
||||
<select id="select-filter-item" class="form-control col"
|
||||
title="<?= lang('select_filter_item_hint') ?>">
|
||||
data-tippy-content="<?= lang('select_filter_item_hint') ?>">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -53,13 +53,13 @@
|
|||
<?php if (($role_slug == DB_SLUG_ADMIN || $role_slug == DB_SLUG_PROVIDER)
|
||||
&& config('google_sync_feature') == TRUE): ?>
|
||||
<button id="google-sync" class="btn btn-primary"
|
||||
title="<?= lang('trigger_google_sync_hint') ?>">
|
||||
data-tippy-content="<?= lang('trigger_google_sync_hint') ?>">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
<span><?= lang('synchronize') ?></span>
|
||||
</button>
|
||||
|
||||
<button id="enable-sync" class="btn btn-light" data-toggle="button"
|
||||
title="<?= lang('enable_appointment_sync_hint') ?>">
|
||||
data-tippy-content="<?= lang('enable_appointment_sync_hint') ?>">
|
||||
<i class="far fa-calendar-alt mr-2"></i>
|
||||
<span><?= lang('enable_sync') ?></span>
|
||||
</button>
|
||||
|
@ -90,20 +90,20 @@
|
|||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<button id="reload-appointments" class="btn btn-light" title="<?= lang('reload_appointments_hint') ?>">
|
||||
<button id="reload-appointments" class="btn btn-light" data-tippy-content="<?= lang('reload_appointments_hint') ?>">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
|
||||
<?php if($calendar_view === 'default'): ?>
|
||||
<a class="btn btn-light" href="<?= site_url('backend?view=table') ?>"
|
||||
title="<?= lang('table') ?>">
|
||||
data-tippy-content="<?= lang('table') ?>">
|
||||
<i class="fas fa-table"></i>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if($calendar_view === 'table'): ?>
|
||||
<a class="btn btn-light" href="<?= site_url('backend?view=default') ?>"
|
||||
title="<?= lang('default') ?>">
|
||||
data-tippy-content="<?= lang('default') ?>">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
|
@ -272,12 +272,12 @@
|
|||
<legend>
|
||||
<?= lang('customer_details_title') ?>
|
||||
<button id="new-customer" class="btn btn-outline-secondary btn-sm" type="button"
|
||||
title="<?= lang('clear_fields_add_existing_customer_hint') ?>">
|
||||
data-tippy-content="<?= lang('clear_fields_add_existing_customer_hint') ?>">
|
||||
<i class="far fa-plus-square mr-2"></i>
|
||||
<?= lang('new') ?>
|
||||
</button>
|
||||
<button id="select-customer" class="btn btn-outline-secondary btn-sm" type="button"
|
||||
title="<?= lang('pick_existing_customer_hint') ?>">
|
||||
data-tippy-content="<?= lang('pick_existing_customer_hint') ?>">
|
||||
<i class="far fa-hand-pointer mr-2"></i>
|
||||
<span>
|
||||
<?= lang('select') ?>
|
||||
|
|
|
@ -35,10 +35,10 @@
|
|||
|
||||
<div class="input-group-addon">
|
||||
<div>
|
||||
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
|
||||
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
|
||||
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
|
||||
<i class="fas fa-redo-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<?= lang('licensed_under') ?> GPLv3 |
|
||||
|
||||
<span id="select-language" class="badge badge-secondary">
|
||||
<i class="fas fa-language mr-2"></i>
|
||||
<?= ucfirst(config('language')) ?>
|
||||
</span>
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/bootstrap/css/bootstrap.min.css') ?>">
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/jquery-ui/jquery-ui.min.css') ?>">
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/jquery-qtip/jquery.qtip.min.css') ?>">
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/trumbowyg/ui/trumbowyg.min.css') ?>">
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/select2/select2.min.css') ?>">
|
||||
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/css/backend.css') ?>">
|
||||
|
@ -25,8 +24,9 @@
|
|||
|
||||
<script src="<?= asset_url('assets/ext/jquery/jquery.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/bootstrap/js/bootstrap.bundle.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/popper/popper.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/tippy/tippy-bundle.umd.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/jquery-ui/jquery-ui.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/jquery-qtip/jquery.qtip.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/moment/moment.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/datejs/date.min.js') ?>"></script>
|
||||
<script src="<?= asset_url('assets/ext/trumbowyg/trumbowyg.min.js') ?>"></script>
|
||||
|
@ -38,7 +38,8 @@
|
|||
<nav id="header" class="navbar navbar-expand-md navbar-dark">
|
||||
<div id="header-logo" class="navbar-brand">
|
||||
<img src="<?= base_url('assets/img/logo.png') ?>">
|
||||
<span>EASY!APPOINTMENTS</span>
|
||||
<h6>EASY!APPOINTMENTS</h6>
|
||||
<small>Open Source Appointment Scheduler</small>
|
||||
</div>
|
||||
|
||||
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#header-menu">
|
||||
|
@ -52,7 +53,7 @@
|
|||
<?php $active = ($active_menu == PRIV_APPOINTMENTS) ? 'active' : '' ?>
|
||||
<li class="nav-item <?= $active . $hidden ?>">
|
||||
<a href="<?= site_url('backend') ?>" class="nav-link"
|
||||
title="<?= lang('manage_appointment_record_hint') ?>">
|
||||
data-tippy-content="<?= lang('manage_appointment_record_hint') ?>">
|
||||
<i class="far fa-calendar-alt mr-2"></i>
|
||||
<?= lang('calendar') ?>
|
||||
</a>
|
||||
|
@ -62,7 +63,7 @@
|
|||
<?php $active = ($active_menu == PRIV_CUSTOMERS) ? 'active' : '' ?>
|
||||
<li class="nav-item <?= $active . $hidden ?>">
|
||||
<a href="<?= site_url('backend/customers') ?>" class="nav-link"
|
||||
title="<?= lang('manage_customers_hint') ?>">
|
||||
data-tippy-content="<?= lang('manage_customers_hint') ?>">
|
||||
<i class="fas fa-user-friends mr-2"></i>
|
||||
<?= lang('customers') ?>
|
||||
</a>
|
||||
|
@ -72,7 +73,7 @@
|
|||
<?php $active = ($active_menu == PRIV_SERVICES) ? 'active' : '' ?>
|
||||
<li class="nav-item <?= $active . $hidden ?>">
|
||||
<a href="<?= site_url('backend/services') ?>" class="nav-link"
|
||||
title="<?= lang('manage_services_hint') ?>">
|
||||
data-tippy-content="<?= lang('manage_services_hint') ?>">
|
||||
<i class="fas fa-business-time mr-2"></i>
|
||||
<?= lang('services') ?>
|
||||
</a>
|
||||
|
@ -82,7 +83,7 @@
|
|||
<?php $active = ($active_menu == PRIV_USERS) ? 'active' : '' ?>
|
||||
<li class="nav-item <?= $active . $hidden ?>">
|
||||
<a href="<?= site_url('backend/users') ?>" class="nav-link"
|
||||
title="<?= lang('manage_users_hint') ?>">
|
||||
data-tippy-content="<?= lang('manage_users_hint') ?>">
|
||||
<i class="fas fa-users-cog mr-2"></i>
|
||||
<?= lang('users') ?>
|
||||
</a>
|
||||
|
@ -93,7 +94,7 @@
|
|||
<?php $active = ($active_menu == PRIV_SYSTEM_SETTINGS) ? 'active' : '' ?>
|
||||
<li class="nav-item <?= $active . $hidden ?>">
|
||||
<a href="<?= site_url('backend/settings') ?>" class="nav-link"
|
||||
title="<?= lang('settings_hint') ?>">
|
||||
data-tippy-content="<?= lang('settings_hint') ?>">
|
||||
<i class="fas fa-cogs mr-2"></i>
|
||||
<?= lang('settings') ?>
|
||||
</a>
|
||||
|
@ -101,7 +102,7 @@
|
|||
|
||||
<li class="nav-item">
|
||||
<a href="<?= site_url('user/logout') ?>" class="nav-link"
|
||||
title="<?= lang('log_out_hint') ?>">
|
||||
data-tippy-content="<?= lang('log_out_hint') ?>">
|
||||
<i class="fas fa-sign-out-alt mr-2"></i>
|
||||
<?= lang('log_out') ?>
|
||||
</a>
|
||||
|
|
|
@ -51,10 +51,10 @@
|
|||
|
||||
<div class="input-group-addon">
|
||||
<div>
|
||||
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
|
||||
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
|
||||
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
|
||||
<i class="fas fa-redo-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -192,10 +192,10 @@
|
|||
|
||||
<div class="input-group-addon">
|
||||
<div>
|
||||
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
|
||||
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
|
||||
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
|
||||
<i class="fas fa-redo-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<?= lang('general_settings') ?>
|
||||
<?php if ($privileges[PRIV_SYSTEM_SETTINGS]['edit'] == TRUE): ?>
|
||||
<button type="button" class="save-settings btn btn-primary btn-sm mb-2"
|
||||
title="<?= lang('save') ?>">
|
||||
data-tippy-content="<?= lang('save') ?>">
|
||||
<i class="far fa-check-square mr-2"></i>
|
||||
<?= lang('save') ?>
|
||||
</button>
|
||||
|
@ -223,7 +223,7 @@
|
|||
<?= lang('business_logic') ?>
|
||||
<?php if ($privileges[PRIV_SYSTEM_SETTINGS]['edit'] == TRUE): ?>
|
||||
<button type="button" class="save-settings btn btn-primary btn-sm mb-2"
|
||||
title="<?= lang('save') ?>">
|
||||
data-tippy-content="<?= lang('save') ?>">
|
||||
<i class="far fa-check-square mr-2"></i>
|
||||
<?= lang('save') ?>
|
||||
</button>
|
||||
|
@ -311,7 +311,7 @@
|
|||
<?= lang('legal_contents') ?>
|
||||
<?php if ($privileges[PRIV_SYSTEM_SETTINGS]['edit'] == TRUE): ?>
|
||||
<button type="button" class="save-settings btn btn-primary btn-sm mb-2"
|
||||
title="<?= lang('save') ?>">
|
||||
data-tippy-content="<?= lang('save') ?>">
|
||||
<i class="far fa-check-square mr-2"></i>
|
||||
<?= lang('save') ?>
|
||||
</button>
|
||||
|
@ -392,7 +392,7 @@
|
|||
<?= lang('personal_information') ?>
|
||||
<?php if ($privileges[PRIV_USER_SETTINGS]['edit'] == TRUE): ?>
|
||||
<button type="button" class="save-settings btn btn-primary btn-sm mb-2"
|
||||
title="<?= lang('save') ?>">
|
||||
data-tippy-content="<?= lang('save') ?>">
|
||||
<i class="far fa-check-square mr-2"></i>
|
||||
<?= lang('save') ?>
|
||||
</button>
|
||||
|
|
|
@ -68,10 +68,10 @@
|
|||
|
||||
<span class="input-group-addon">
|
||||
<div>
|
||||
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
|
||||
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
|
||||
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
|
||||
<i class="fas fa-redo-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -284,7 +284,7 @@
|
|||
<div class="working-plan-view tab-pane fade clearfix" id="working-plan">
|
||||
<h3><?= lang('working_plan') ?></h3>
|
||||
<button id="reset-working-plan" class="btn btn-primary"
|
||||
title="<?= lang('reset_working_plan') ?>">
|
||||
data-tippy-content="<?= lang('reset_working_plan') ?>">
|
||||
<i class="fas fa-redo-alt mr-2"></i>
|
||||
<?= lang('reset_plan') ?></button>
|
||||
<table class="working-plan table table-striped mt-2">
|
||||
|
@ -374,10 +374,10 @@
|
|||
|
||||
<span class="input-group-addon">
|
||||
<div>
|
||||
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
|
||||
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
|
||||
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
|
||||
<i class="fas fa-redo-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -580,10 +580,10 @@
|
|||
|
||||
<span class="input-group-addon">
|
||||
<div>
|
||||
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
|
||||
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
|
||||
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
|
||||
<i class="fas fa-redo-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
</div>
|
||||
|
||||
<header>
|
||||
<a href="https://easyappointments.org" target="_blank">
|
||||
<img src="<?= base_url('assets/img/installation-banner.png') ?>"
|
||||
alt="Easy!Appointments Installation Banner">
|
||||
</a>
|
||||
<div class="container">
|
||||
<h1 class="page-title">Easy!Appointments Installation</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content container-fluid">
|
||||
|
|
|
@ -29,6 +29,12 @@ root {
|
|||
|
||||
#header #header-logo {
|
||||
padding: 5px;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
#header #header-logo small {
|
||||
font-size: 9px;
|
||||
color: #2e6a5b;
|
||||
}
|
||||
|
||||
#header #header-logo img {
|
||||
|
@ -38,12 +44,11 @@ root {
|
|||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#header #header-logo span {
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
#header #header-logo h6 {
|
||||
margin-top: 6px;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
#header .navbar-toggler {
|
||||
|
|
|
@ -147,6 +147,7 @@ body {
|
|||
text-align: center;
|
||||
color: #0bb98d;
|
||||
transition: all .3s linear;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#book-appointment-wizard .active-step {
|
||||
|
@ -171,10 +172,6 @@ body {
|
|||
border-top: 1px solid #ebeef1;
|
||||
}
|
||||
|
||||
#book-appointment-wizard #steps .custom-qtip {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
#book-appointment-wizard #available-hours {
|
||||
overflow: auto;
|
||||
margin: 15px 0;
|
||||
|
|
|
@ -15,17 +15,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
body .custom-qtip {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 10px;
|
||||
box-shadow: 1px 1px 3px #767676;
|
||||
background: #EFFDF6;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #258D53;
|
||||
}
|
||||
|
||||
body .ui-widget {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-size: 1.1em;
|
||||
|
|
|
@ -3,11 +3,21 @@ header {
|
|||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
header .page-title {
|
||||
font-weight: lighter;
|
||||
padding: 40px 0;
|
||||
color: #429a82;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 32px;
|
||||
max-width: 980px;
|
||||
}
|
||||
|
||||
.content p {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin: 25px 0 10px 0;
|
||||
}
|
||||
|
|
1
assets/ext/jquery-qtip/jquery.qtip.min.css
vendored
5
assets/ext/jquery-qtip/jquery.qtip.min.js
vendored
6
assets/ext/popper/popper.min.js
vendored
Normal file
2
assets/ext/tippy/tippy-bundle.umd.min.js
vendored
Normal file
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 12 KiB |
|
@ -25,7 +25,7 @@ window.Backend = window.Backend || {};
|
|||
/**
|
||||
* Main javascript code for the backend of Easy!Appointments.
|
||||
*/
|
||||
$(document).ready(function () {
|
||||
$(function () {
|
||||
$(window)
|
||||
.on('resize', function () {
|
||||
Backend.placeFooterToBottom();
|
||||
|
@ -40,15 +40,7 @@ window.Backend = window.Backend || {};
|
|||
$('#loading').hide();
|
||||
});
|
||||
|
||||
$('.menu-item').qtip({
|
||||
position: {
|
||||
my: 'top center',
|
||||
at: 'bottom center'
|
||||
},
|
||||
style: {
|
||||
classes: 'qtip-green qtip-shadow custom-qtip'
|
||||
}
|
||||
});
|
||||
tippy('[data-tippy-content]');
|
||||
|
||||
GeneralFunctions.enableLanguageSelection($('#select-language'));
|
||||
});
|
||||
|
|
|
@ -114,7 +114,7 @@ window.BackendCalendar = window.BackendCalendar || {};
|
|||
exports.initialize = function (view) {
|
||||
BackendCalendarGoogleSync.initialize();
|
||||
BackendCalendarAppointmentsModal.initialize();
|
||||
BackendCalendarUnavailabilitiesModal.initialize();
|
||||
BackendCalendarUnavailabilityEventsModal.initialize();
|
||||
|
||||
// Load and initialize the calendar view.
|
||||
if (view === 'table') {
|
||||
|
|
|
@ -142,7 +142,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
endDatetime = Date.parseExact(unavailable.end_datetime, 'yyyy-MM-dd HH:mm:ss');
|
||||
|
||||
$dialog = $('#manage-unavailable');
|
||||
BackendCalendarUnavailabilitiesModal.resetUnavailableDialog();
|
||||
BackendCalendarUnavailabilityEventsModal.resetUnavailableDialog();
|
||||
|
||||
// Apply unavailable data to dialog.
|
||||
$dialog.find('.modal-header h3').text('Edit Unavailable Period');
|
||||
|
@ -1066,7 +1066,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
|
||||
// Add custom unavailable periods (they are always displayed on the calendar, even if the provider won't
|
||||
// work on that day).
|
||||
var unavailableEvents = [];
|
||||
var unavailabilityEvents = [];
|
||||
response.unavailables.forEach(function (unavailable) {
|
||||
var notes = unavailable.notes ? ' - ' + unavailable.notes : '';
|
||||
|
||||
|
@ -1074,7 +1074,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
notes = unavailable.notes.substring(0, 30) + '...'
|
||||
}
|
||||
|
||||
var unavailableEvent = {
|
||||
var unavailabilityEvent = {
|
||||
title: EALang.unavailable + notes,
|
||||
start: moment(unavailable.start_datetime),
|
||||
end: moment(unavailable.end_datetime),
|
||||
|
@ -1085,10 +1085,10 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
data: unavailable
|
||||
};
|
||||
|
||||
unavailableEvents.push(unavailableEvent);
|
||||
unavailabilityEvents.push(unavailabilityEvent);
|
||||
});
|
||||
|
||||
$calendar.fullCalendar('addEventSource', unavailableEvents);
|
||||
$calendar.fullCalendar('addEventSource', unavailabilityEvents);
|
||||
|
||||
var calendarView = $('#calendar').fullCalendar('getView');
|
||||
|
||||
|
@ -1103,7 +1103,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
|
||||
var workingPlan = jQuery.parseJSON(provider.settings.working_plan);
|
||||
var workingPlanExceptions = jQuery.parseJSON(provider.settings.working_plan_exceptions);
|
||||
var unavailableEvent;
|
||||
var unavailabilityEvent;
|
||||
var viewStart;
|
||||
var viewEnd;
|
||||
var breakStart;
|
||||
|
@ -1157,7 +1157,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
// Non-working day.
|
||||
if (sortedWorkingPlan[weekdayName] === null) {
|
||||
// Working plan exception.
|
||||
unavailableEvent = {
|
||||
unavailabilityEvent = {
|
||||
title: EALang.not_working,
|
||||
start: calendarView.intervalStart.clone(),
|
||||
end: calendarView.intervalEnd.clone(),
|
||||
|
@ -1167,7 +1167,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
className: 'fc-unavailable'
|
||||
};
|
||||
|
||||
$calendar.fullCalendar('renderEvent', unavailableEvent, false);
|
||||
$calendar.fullCalendar('renderEvent', unavailabilityEvent, false);
|
||||
|
||||
return; // Go to next loop.
|
||||
}
|
||||
|
@ -1276,7 +1276,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
// Non-working day.
|
||||
if (sortedWorkingPlan[weekdayName] === null) {
|
||||
// Add a full day unavailable event.
|
||||
unavailableEvent = {
|
||||
unavailabilityEvent = {
|
||||
title: EALang.not_working,
|
||||
start: calendarDate.clone(),
|
||||
end: calendarDate.clone().add(1, 'day'),
|
||||
|
@ -1286,7 +1286,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
className: 'fc-unavailable'
|
||||
};
|
||||
|
||||
$calendar.fullCalendar('renderEvent', unavailableEvent, true);
|
||||
$calendar.fullCalendar('renderEvent', unavailabilityEvent, true);
|
||||
|
||||
calendarDate.add(1, 'day');
|
||||
|
||||
|
@ -1300,7 +1300,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
workDateStart.minute(parseInt(startHour[1]));
|
||||
|
||||
if (calendarDate < workDateStart) {
|
||||
unavailableEvent = {
|
||||
unavailabilityEvent = {
|
||||
title: EALang.not_working,
|
||||
start: calendarDate.clone(),
|
||||
end: moment(calendarDate.format('YYYY-MM-DD') + ' ' + sortedWorkingPlan[weekdayName].start + ':00'),
|
||||
|
@ -1310,7 +1310,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
className: 'fc-unavailable'
|
||||
};
|
||||
|
||||
$calendar.fullCalendar('renderEvent', unavailableEvent, true);
|
||||
$calendar.fullCalendar('renderEvent', unavailabilityEvent, true);
|
||||
}
|
||||
|
||||
// Add unavailable period after work ends.
|
||||
|
@ -1320,7 +1320,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
workDateEnd.minute(parseInt(endHour[1]));
|
||||
|
||||
if (calendarView.end > workDateEnd) {
|
||||
unavailableEvent = {
|
||||
unavailabilityEvent = {
|
||||
title: EALang.not_working,
|
||||
start: moment(calendarDate.format('YYYY-MM-DD') + ' ' + sortedWorkingPlan[weekdayName].end + ':00'),
|
||||
end: calendarDate.clone().add(1, 'day'),
|
||||
|
@ -1330,7 +1330,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
className: 'fc-unavailable'
|
||||
};
|
||||
|
||||
$calendar.fullCalendar('renderEvent', unavailableEvent, false);
|
||||
$calendar.fullCalendar('renderEvent', unavailabilityEvent, false);
|
||||
}
|
||||
|
||||
// Add unavailable periods during day breaks.
|
||||
|
@ -1345,7 +1345,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
breakEnd.hour(parseInt(breakEndString[0]));
|
||||
breakEnd.minute(parseInt(breakEndString[1]));
|
||||
|
||||
var unavailableEvent = {
|
||||
var unavailabilityEvent = {
|
||||
title: EALang.break,
|
||||
start: moment(calendarDate.format('YYYY-MM-DD') + ' ' + breakPeriod.start),
|
||||
end: moment(calendarDate.format('YYYY-MM-DD') + ' ' + breakPeriod.end),
|
||||
|
@ -1355,7 +1355,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
className: 'fc-unavailable fc-break'
|
||||
};
|
||||
|
||||
$calendar.fullCalendar('renderEvent', unavailableEvent, false);
|
||||
$calendar.fullCalendar('renderEvent', unavailabilityEvent, false);
|
||||
});
|
||||
|
||||
calendarDate.add(1, 'day');
|
||||
|
@ -1612,27 +1612,6 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
|
|||
$dialog.modal('show');
|
||||
}
|
||||
|
||||
// Apply qtip to control tooltips.
|
||||
$('#calendar-toolbar button').qtip({
|
||||
position: {
|
||||
my: 'top center',
|
||||
at: 'bottom center'
|
||||
},
|
||||
style: {
|
||||
classes: 'qtip-green qtip-shadow custom-qtip'
|
||||
}
|
||||
});
|
||||
|
||||
$('#select-filter-item').qtip({
|
||||
position: {
|
||||
my: 'middle left',
|
||||
at: 'middle right'
|
||||
},
|
||||
style: {
|
||||
classes: 'qtip-green qtip-shadow custom-qtip'
|
||||
}
|
||||
});
|
||||
|
||||
if (!$('#select-filter-item option').length) {
|
||||
$('#calendar-actions button').prop('disabled', true);
|
||||
}
|
||||
|
|
|
@ -91,13 +91,16 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
|
||||
$providerColumn.find('.calendar-wrapper').fullCalendar('removeEvents');
|
||||
|
||||
createNonWorkingHours($providerColumn.find('.calendar-wrapper'), JSON.parse($providerColumn.data('provider').settings.working_plan));
|
||||
createNonWorkingHours(
|
||||
$providerColumn.find('.calendar-wrapper'),
|
||||
$providerColumn.data('provider')
|
||||
);
|
||||
|
||||
// Add the appointments to the column.
|
||||
createAppointments($providerColumn, response.appointments);
|
||||
|
||||
// Add the unavailabilities to the column.
|
||||
createUnavailabilities($providerColumn, response.unavailabilities);
|
||||
// Add the unavailability events to the column.
|
||||
createUnavailabilityEvents($providerColumn, response.unavailability_events);
|
||||
|
||||
// Add the provider breaks to the column.
|
||||
var workingPlan = JSON.parse(provider.settings.working_plan);
|
||||
|
@ -145,7 +148,36 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
|
||||
var $dialog;
|
||||
|
||||
if (lastFocusedEventData.data.is_unavailable === '0') {
|
||||
if (lastFocusedEventData.data.workingPlanException) {
|
||||
var date = lastFocusedEventData.data.date;
|
||||
var workingPlanException = lastFocusedEventData.data.workingPlanException;
|
||||
var provider = lastFocusedEventData.data.provider;
|
||||
|
||||
WorkingPlanExceptionsModal
|
||||
.edit(date, workingPlanException)
|
||||
.done(function (date, workingPlanException) {
|
||||
var successCallback = function () {
|
||||
Backend.displayNotification(EALang.working_plan_exception_saved);
|
||||
|
||||
var workingPlanExceptions = jQuery.parseJSON(provider.settings.working_plan_exceptions) || {};
|
||||
|
||||
workingPlanExceptions[date] = workingPlanException;
|
||||
|
||||
for (var index in GlobalVariables.availableProviders) {
|
||||
var availableProvider = GlobalVariables.availableProviders[index];
|
||||
|
||||
if (Number(availableProvider.id) === Number(provider.id)) {
|
||||
availableProvider.settings.working_plan_exceptions = JSON.stringify(workingPlanExceptions);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$('#select-filter-item').trigger('change'); // Update the calendar.
|
||||
};
|
||||
|
||||
BackendCalendarApi.saveWorkingPlanException(date, workingPlanException, provider.id, successCallback, null);
|
||||
});
|
||||
} else if (lastFocusedEventData.data.is_unavailable === '0') {
|
||||
var appointment = lastFocusedEventData.data;
|
||||
$dialog = $('#manage-appointment');
|
||||
|
||||
|
@ -186,7 +218,7 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
var endDatetime = Date.parseExact(unavailable.end_datetime, 'yyyy-MM-dd HH:mm:ss');
|
||||
|
||||
$dialog = $('#manage-unavailable');
|
||||
BackendCalendarUnavailabilitiesModal.resetUnavailableDialog();
|
||||
BackendCalendarUnavailabilityEventsModal.resetUnavailableDialog();
|
||||
|
||||
// Apply unavailable data to dialog.
|
||||
$dialog.find('.modal-header h3').text('Edit Unavailable Period');
|
||||
|
@ -592,13 +624,16 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
createCalendar($providerColumn, date, provider);
|
||||
|
||||
// Create non working hours.
|
||||
createNonWorkingHours($providerColumn.find('.calendar-wrapper'), JSON.parse(provider.settings.working_plan))
|
||||
createNonWorkingHours(
|
||||
$providerColumn.find('.calendar-wrapper'),
|
||||
provider
|
||||
);
|
||||
|
||||
// Add the appointments to the column.
|
||||
createAppointments($providerColumn, events.appointments);
|
||||
|
||||
// Add the unavailabilities to the column.
|
||||
createUnavailabilities($providerColumn, events.unavailabilities);
|
||||
// Add the unavailability events to the column.
|
||||
createUnavailabilityEvents($providerColumn, events.unavailability_events);
|
||||
|
||||
Backend.placeFooterToBottom();
|
||||
}
|
||||
|
@ -606,8 +641,8 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
/**
|
||||
* Get Calendar Component Height
|
||||
*
|
||||
* This method calculates the proper calendar height, in order to be displayed correctly, even when the
|
||||
* browser window is resizing.
|
||||
* This method calculates the proper calendar height, in order to be displayed correctly, even when the browser
|
||||
* window is resizing.
|
||||
*
|
||||
* @return {Number} Returns the calendar element height in pixels.
|
||||
*/
|
||||
|
@ -793,11 +828,38 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
$(element).fullCalendar('option', 'height', getCalendarHeight());
|
||||
}
|
||||
|
||||
function createNonWorkingHours($calendar, workingPlan) {
|
||||
function createNonWorkingHours($calendar, provider) {
|
||||
var workingPlan = JSON.parse(provider.settings.working_plan);
|
||||
var workingPlanExceptions = JSON.parse(provider.settings.working_plan_exceptions);
|
||||
var view = $calendar.fullCalendar('getView');
|
||||
var start = view.start.clone();
|
||||
var end = view.end.clone();
|
||||
var selDayName = start.toDate().toString('dddd').toLowerCase();
|
||||
var selDayDate = start.format('YYYY-MM-DD');
|
||||
|
||||
if (workingPlanExceptions[selDayDate]) {
|
||||
workingPlan[selDayName] = workingPlanExceptions[selDayDate];
|
||||
|
||||
var workingPlanExceptionStart = selDayDate + ' ' + workingPlan[selDayName].start;
|
||||
var workingPlanExceptionEnd = selDayDate + ' ' + workingPlan[selDayName].end;
|
||||
|
||||
var workingPlanExceptionEvent = {
|
||||
title: EALang.working_plan_exception,
|
||||
start: moment(workingPlanExceptionStart, 'YYYY-MM-DD HH:mm', true),
|
||||
end: moment(workingPlanExceptionEnd, 'YYYY-MM-DD HH:mm', true).add(1, 'day'),
|
||||
allDay: true,
|
||||
color: '#879DB4',
|
||||
editable: false,
|
||||
className: 'fc-working-plan-exception fc-custom',
|
||||
data: {
|
||||
date: selDayDate,
|
||||
workingPlanException: workingPlanExceptions[selDayDate],
|
||||
provider: provider
|
||||
}
|
||||
};
|
||||
|
||||
$calendar.fullCalendar('renderEvent', workingPlanExceptionEvent, false);
|
||||
}
|
||||
|
||||
if (workingPlan[selDayName] === null) {
|
||||
var nonWorkingDay = {
|
||||
|
@ -912,20 +974,20 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
}
|
||||
|
||||
/**
|
||||
* Create Unavailabilities Events
|
||||
* Create Unavailability Events
|
||||
*
|
||||
* This method will add the unavailability events on the table view.
|
||||
*
|
||||
* @param {jQuery} $providerColumn The provider column container.
|
||||
* @param {Object[]} unavailabilities Contains the unavailability events data.
|
||||
* @param {Object[]} unavailabilityEvents Contains the unavailability events data.
|
||||
*/
|
||||
function createUnavailabilities($providerColumn, unavailabilities) {
|
||||
if (unavailabilities.length === 0) {
|
||||
function createUnavailabilityEvents($providerColumn, unavailabilityEvents) {
|
||||
if (unavailabilityEvents.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var index in unavailabilities) {
|
||||
var unavailability = unavailabilities[index];
|
||||
for (var index in unavailabilityEvents) {
|
||||
var unavailability = unavailabilityEvents[index];
|
||||
|
||||
if (unavailability.id_users_provider !== $providerColumn.data('provider').id) {
|
||||
continue;
|
||||
|
@ -1036,10 +1098,10 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
if ($(this).hasClass('fc-unavailable') || $parent.hasClass('fc-unavailable') || $altParent.hasClass('fc-unavailable')) {
|
||||
displayEdit = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
|
||||
&& GlobalVariables.user.privileges.appointments.edit === true)
|
||||
? 'mr-2' : 'd-none';
|
||||
? '' : 'd-none';
|
||||
displayDelete = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
|
||||
&& GlobalVariables.user.privileges.appointments.delete === true)
|
||||
? 'mr-2' : 'd-none'; // Same value at the time.
|
||||
? '' : 'd-none'; // Same value at the time.
|
||||
|
||||
$html = $('<div/>', {
|
||||
'html': [
|
||||
|
@ -1070,8 +1132,30 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
$('<hr/>'),
|
||||
|
||||
$('<div/>', {
|
||||
'class': 'd-flex justify-content-between',
|
||||
'class': 'd-flex justify-content-center',
|
||||
'html': [
|
||||
$('<button/>', {
|
||||
'class': 'close-popover btn btn-outline-secondary mr-2',
|
||||
'html': [
|
||||
$('<i/>', {
|
||||
'class': 'fas fa-ban mr-2'
|
||||
}),
|
||||
$('<span/>', {
|
||||
'text': EALang.close
|
||||
})
|
||||
]
|
||||
}),
|
||||
$('<button/>', {
|
||||
'class': 'delete-popover btn btn-outline-secondary mr-2 ' + displayDelete,
|
||||
'html': [
|
||||
$('<i/>', {
|
||||
'class': 'far fa-trash-alt mr-2'
|
||||
}),
|
||||
$('<span/>', {
|
||||
'text': EALang.delete
|
||||
})
|
||||
]
|
||||
}),
|
||||
$('<button/>', {
|
||||
'class': 'edit-popover btn btn-primary ' + displayEdit,
|
||||
'html': [
|
||||
|
@ -1083,28 +1167,6 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
})
|
||||
]
|
||||
}),
|
||||
$('<button/>', {
|
||||
'class': 'delete-popover btn btn-outline-secondary ' + displayDelete,
|
||||
'html': [
|
||||
$('<i/>', {
|
||||
'class': 'far fa-trash-alt mr-2'
|
||||
}),
|
||||
$('<span/>', {
|
||||
'text': EALang.delete
|
||||
})
|
||||
]
|
||||
}),
|
||||
$('<button/>', {
|
||||
'class': 'close-popover btn btn-outline-secondary',
|
||||
'html': [
|
||||
$('<i/>', {
|
||||
'class': 'fas fa-ban mr-2'
|
||||
}),
|
||||
$('<span/>', {
|
||||
'text': EALang.close
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
|
@ -1112,11 +1174,11 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
} else if ($(this).hasClass('fc-working-plan-exception') || $parent.hasClass('fc-working-plan-exception') || $altParent.hasClass('fc-working-plan-exception')) {
|
||||
displayEdit = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
|
||||
&& GlobalVariables.user.privileges.appointments.edit === true)
|
||||
? 'mr-2' : 'd-none'; // Same value at the time.
|
||||
? '' : 'd-none'; // Same value at the time.
|
||||
|
||||
displayDelete = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
|
||||
&& GlobalVariables.user.privileges.appointments.delete === true)
|
||||
? 'mr-2' : 'd-none'; // Same value at the time.
|
||||
? '' : 'd-none'; // Same value at the time.
|
||||
|
||||
$html = $('<div/>', {
|
||||
'html': [
|
||||
|
@ -1124,7 +1186,7 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
'text': EALang.provider
|
||||
}),
|
||||
$('<span/>', {
|
||||
'text': event.data ? event.data.first_name + ' ' + event.data.last_name : '-'
|
||||
'text': event.data ? event.data.provider.first_name + ' ' + event.data.provider.last_name : '-'
|
||||
}),
|
||||
$('<br/>'),
|
||||
|
||||
|
@ -1158,18 +1220,18 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
'class': 'd-flex justify-content-center',
|
||||
'html': [
|
||||
$('<button/>', {
|
||||
'class': 'edit-popover btn btn-danger ' + displayEdit,
|
||||
'class': 'close-popover btn btn-outline-secondary mr-2',
|
||||
'html': [
|
||||
$('<i/>', {
|
||||
'class': 'far fa-edit mr-2'
|
||||
'class': 'fas fa-ban mr-2'
|
||||
}),
|
||||
$('<span/>', {
|
||||
'text': EALang.edit
|
||||
'text': EALang.close
|
||||
})
|
||||
]
|
||||
}),
|
||||
$('<button/>', {
|
||||
'class': 'delete-popover btn btn-outline-secondary ' + displayDelete,
|
||||
'class': 'delete-popover btn btn-outline-secondary mr-2 ' + displayDelete,
|
||||
'html': [
|
||||
$('<i/>', {
|
||||
'class': 'far fa-trash-alt mr-2'
|
||||
|
@ -1180,13 +1242,13 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
]
|
||||
}),
|
||||
$('<button/>', {
|
||||
'class': 'close-popover btn btn-outline-secondary',
|
||||
'class': 'edit-popover btn btn-primary ' + displayEdit,
|
||||
'html': [
|
||||
$('<i/>', {
|
||||
'class': 'fas fa-ban mr-2'
|
||||
'class': 'far fa-edit mr-2'
|
||||
}),
|
||||
$('<span/>', {
|
||||
'text': EALang.close
|
||||
'text': EALang.edit
|
||||
})
|
||||
]
|
||||
})
|
||||
|
@ -1196,9 +1258,9 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
});
|
||||
} else {
|
||||
displayEdit = (GlobalVariables.user.privileges.appointments.edit === true)
|
||||
? 'mr-2' : 'd-none';
|
||||
? '' : 'd-none';
|
||||
displayDelete = (GlobalVariables.user.privileges.appointments.delete === true)
|
||||
? 'mr-2' : 'd-none';
|
||||
? '' : 'd-none';
|
||||
|
||||
$html = $('<div/>', {
|
||||
'html': [
|
||||
|
@ -1284,18 +1346,18 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
'class': 'd-flex justify-content-center',
|
||||
'html': [
|
||||
$('<button/>', {
|
||||
'class': 'edit-popover btn btn-primary ' + displayEdit,
|
||||
'class': 'close-popover btn btn-outline-secondary mr-2',
|
||||
'html': [
|
||||
$('<i/>', {
|
||||
'class': 'far fa-edit mr-2'
|
||||
'class': 'fas fa-ban mr-2'
|
||||
}),
|
||||
$('<span/>', {
|
||||
'text': EALang.edit
|
||||
'text': EALang.close
|
||||
})
|
||||
]
|
||||
}),
|
||||
$('<button/>', {
|
||||
'class': 'delete-popover btn btn-outline-secondary ' + displayDelete,
|
||||
'class': 'delete-popover btn btn-outline-secondary mr-2' + displayDelete,
|
||||
'html': [
|
||||
$('<i/>', {
|
||||
'class': 'far fa-trash-alt mr-2'
|
||||
|
@ -1306,13 +1368,13 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
]
|
||||
}),
|
||||
$('<button/>', {
|
||||
'class': 'close-popover btn btn-outline-secondary',
|
||||
'class': 'edit-popover btn btn-primary ' + displayEdit,
|
||||
'html': [
|
||||
$('<i/>', {
|
||||
'class': 'fas fa-ban mr-2'
|
||||
'class': 'far fa-edit mr-2'
|
||||
}),
|
||||
$('<span/>', {
|
||||
'text': EALang.close
|
||||
'text': EALang.edit
|
||||
})
|
||||
]
|
||||
})
|
||||
|
@ -1679,6 +1741,8 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
|
|||
|
||||
createView(startDate, endDate);
|
||||
|
||||
$('#insert-working-plan-exception').hide();
|
||||
|
||||
bindEventHandlers();
|
||||
|
||||
// Hide Google Calendar Sync buttons cause they can not be used within this view.
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Backend Calendar Unavailabilities Modal
|
||||
* Backend Calendar Unavailability Events Modal
|
||||
*
|
||||
* This module implements the unavailabilities modal functionality.
|
||||
* This module implements the unavailability events modal functionality.
|
||||
*
|
||||
* @module BackendCalendarUnavailabilitiesModal
|
||||
* @module BackendCalendarUnavailabilityEventsModal
|
||||
*/
|
||||
window.BackendCalendarUnavailabilitiesModal = window.BackendCalendarUnavailabilitiesModal || {};
|
||||
window.BackendCalendarUnavailabilityEventsModal = window.BackendCalendarUnavailabilityEventsModal || {};
|
||||
|
||||
(function (exports) {
|
||||
|
||||
|
@ -91,7 +91,7 @@ window.BackendCalendarUnavailabilitiesModal = window.BackendCalendarUnavailabili
|
|||
* he cannot accept any appointments.
|
||||
*/
|
||||
$('#insert-unavailable').on('click', function () {
|
||||
BackendCalendarUnavailabilitiesModal.resetUnavailableDialog();
|
||||
BackendCalendarUnavailabilityEventsModal.resetUnavailableDialog();
|
||||
var $dialog = $('#manage-unavailable');
|
||||
|
||||
// Set the default datetime values.
|
||||
|
@ -229,4 +229,4 @@ window.BackendCalendarUnavailabilitiesModal = window.BackendCalendarUnavailabili
|
|||
bindEventHandlers();
|
||||
};
|
||||
|
||||
})(window.BackendCalendarUnavailabilitiesModal);
|
||||
})(window.BackendCalendarUnavailabilityEventsModal);
|
|
@ -113,16 +113,6 @@ window.BackendUsers = window.BackendUsers || {};
|
|||
.appendTo('#secretary-providers');
|
||||
});
|
||||
|
||||
$('#reset-working-plan').qtip({
|
||||
position: {
|
||||
my: 'top center',
|
||||
at: 'bottom center'
|
||||
},
|
||||
style: {
|
||||
classes: 'qtip-green qtip-shadow custom-qtip'
|
||||
}
|
||||
});
|
||||
|
||||
// Bind event handlers.
|
||||
if (defaultEventHandlers) {
|
||||
bindEventHandlers();
|
||||
|
|
|
@ -89,15 +89,7 @@ window.FrontendBook = window.FrontendBook || {};
|
|||
FrontendBook.manageMode = manageMode;
|
||||
|
||||
// Initialize page's components (tooltips, datepickers etc).
|
||||
$('.book-step').qtip({
|
||||
position: {
|
||||
my: 'top center',
|
||||
at: 'bottom center'
|
||||
},
|
||||
style: {
|
||||
classes: 'qtip-green qtip-shadow custom-qtip'
|
||||
}
|
||||
});
|
||||
tippy('[data-tippy-content]');
|
||||
|
||||
var weekDayId = GeneralFunctions.getWeekDayId(GlobalVariables.firstWeekday);
|
||||
|
||||
|
@ -632,7 +624,7 @@ window.FrontendBook = window.FrontendBook || {};
|
|||
|
||||
data.appointment = {
|
||||
start_datetime: $('#select-date').datepicker('getDate').toString('yyyy-MM-dd')
|
||||
+ ' ' + Date.parse($('.selected-hour').text()).toString('HH:mm') + ':00',
|
||||
+ ' ' + Date.parse($('.selected-hour').data('value') || '').toString('HH:mm') + ':00',
|
||||
end_datetime: calculateEndDatetime(),
|
||||
notes: $('#notes').val(),
|
||||
is_unavailable: false,
|
||||
|
@ -666,7 +658,7 @@ window.FrontendBook = window.FrontendBook || {};
|
|||
|
||||
// Add the duration to the start datetime.
|
||||
var startDatetime = $('#select-date').datepicker('getDate').toString('dd-MM-yyyy')
|
||||
+ ' ' + Date.parse($('.selected-hour').text()).toString('HH:mm');
|
||||
+ ' ' + Date.parse($('.selected-hour').data('value') || '').toString('HH:mm');
|
||||
startDatetime = Date.parseExact(startDatetime, 'dd-MM-yyyy HH:mm');
|
||||
var endDatetime;
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ window.FrontendBookApi = window.FrontendBookApi || {};
|
|||
var selectedTimezone = $('#select-timezone').val();
|
||||
var timeFormat = GlobalVariables.timeFormat === 'regular' ? 'h:mm a' : 'HH:mm';
|
||||
|
||||
response.forEach(function (availableHour, index) {
|
||||
response.forEach(function (availableHour) {
|
||||
var availableHourMoment = moment
|
||||
.tz(selectedDate + ' ' + availableHour + ':00', providerTimezone)
|
||||
.tz(selectedTimezone);
|
||||
|
@ -98,6 +98,9 @@ window.FrontendBookApi = window.FrontendBookApi || {};
|
|||
$('#available-hours').append(
|
||||
$('<button/>', {
|
||||
'class': 'btn btn-outline-secondary btn-block shadow-none available-hour',
|
||||
'data': {
|
||||
'value': availableHour
|
||||
},
|
||||
'text': availableHourMoment.format(timeFormat)
|
||||
})
|
||||
);
|
||||
|
|
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 5.4 KiB |
9510
package-lock.json
generated
13
package.json
|
@ -24,16 +24,17 @@
|
|||
"node": ">=12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.14.0",
|
||||
"bootstrap": "^4.5.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||
"bootstrap": "^4.5.3",
|
||||
"cookieconsent": "^3.1.1",
|
||||
"datejs": "0.0.2",
|
||||
"fullcalendar": "^3.10.1",
|
||||
"fullcalendar": "^3.10.2",
|
||||
"jquery": "^3.5.1",
|
||||
"jquery-ui": "^1.12.0",
|
||||
"moment": "^2.25.3",
|
||||
"moment-timezone": "^0.5.28",
|
||||
"moment": "^2.29.1",
|
||||
"moment-timezone": "^0.5.31",
|
||||
"qtip2": "^3.0.3",
|
||||
"tippy.js": "^6.2.7",
|
||||
"trumbowyg": "^2.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -47,7 +48,7 @@
|
|||
"gulp-plumber": "^1.2.1",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"jsdoc": "^3.6.4",
|
||||
"jsdoc": "^3.6.6",
|
||||
"node-notifier": "^5.4.3",
|
||||
"plato": "^1.7.0",
|
||||
"zip-dir": "^1.0.2"
|
||||
|
|