* Finished with unavailable time periods management (backend).

* Started google sync operation (complete sync).
* Minor changes on js files.
This commit is contained in:
alextselegidis@gmail.com 2013-07-10 13:57:24 +00:00
parent f5250e5581
commit 8455891262
15 changed files with 818 additions and 324 deletions

View file

@ -30,29 +30,25 @@ class Appointments extends CI_Controller {
// Load the appointments data and enable the manage mode of the page.
$manage_mode = TRUE;
$appointments_results = $this->appointments_model
->get_batch(array('hash' => $appointment_hash));
$results = $this->appointments_model->get_batch(array('hash' => $appointment_hash));
if (count($appointments_results) === 0) {
if (count($results) === 0) {
// The requested appointment doesn't exist in the database. Display
// a message to the customer.
$view_data = array(
$view = array(
'message_title' => 'Appointment Not Found!',
'message_text' => 'The appointment you requested does not exist in '
. 'the system database anymore.',
'message_icon' => $this->config->item('base_url')
. 'assets/images/error.png'
);
$this->load->view('appointments/message', $view_data);
$this->load->view('appointments/message', $view);
return;
}
$appointment = $appointments_results[0];
$provider = $this->providers_model
->get_row($appointment['id_users_provider']);
$customer = $this->customers_model
->get_row($appointment['id_users_customer']);
$appointment = $results[0];
$provider = $this->providers_model->get_row($appointment['id_users_provider']);
$customer = $this->customers_model->get_row($appointment['id_users_customer']);
} else {
// The customer is going to book a new appointment so there is no
@ -64,7 +60,7 @@ class Appointments extends CI_Controller {
}
// Load the book appointment view.
$view_data = array (
$view = array (
'available_services' => $available_services,
'available_providers' => $available_providers,
'company_name' => $company_name,
@ -75,10 +71,10 @@ class Appointments extends CI_Controller {
);
} catch(Exception $exc) {
$view_data['exceptions'][] = $exc;
$view['exceptions'][] = $exc;
}
$this->load->view('appointments/book', $view_data);
$this->load->view('appointments/book', $view);
} else {
// The page is a post-back. Register the appointment and send notification emails
@ -135,7 +131,7 @@ class Appointments extends CI_Controller {
}
}
} catch(Exception $exc) {
$view_data['exceptions'][] = $exc;
$view['exceptions'][] = $exc;
}
// :: SEND NOTIFICATION EMAILS TO BOTH CUSTOMER AND PROVIDER
@ -175,11 +171,11 @@ class Appointments extends CI_Controller {
$service, $customer, $company_settings, $provider_title,
$provider_message, $provider_link, $provider['email']);
} catch(Exception $exc) {
$view_data['exceptions'][] = $exc;
$view['exceptions'][] = $exc;
}
// :: LOAD THE BOOK SUCCESS VIEW
$view_data = array(
$view = array(
'appointment_data' => $appointment,
'provider_data' => $provider,
'service_data' => $service,
@ -187,10 +183,10 @@ class Appointments extends CI_Controller {
);
} catch(Exception $exc) {
$view_data['exceptions'][] = $exc;
$view['exceptions'][] = $exc;
}
$this->load->view('appointments/book_success', $view_data);
$this->load->view('appointments/book_success', $view);
}
}
@ -273,10 +269,10 @@ class Appointments extends CI_Controller {
}
if (isset($exceptions)) {
$view_data['exceptions'] = $exceptions;
$view['exceptions'] = $exceptions;
}
$this->load->view('appointments/cancel', $view_data);
$this->load->view('appointments/cancel', $view);
}
/**
@ -451,12 +447,12 @@ class Appointments extends CI_Controller {
$this->load->model('providers_model');
// Get the provider's working plan and reserved appointments.
$working_plan = json_decode($this->providers_model
->get_setting('working_plan', $provider_id), true);
$working_plan = json_decode($this->providers_model->get_setting('working_plan',
$provider_id), true);
$where_clause = array(
'DATE(start_datetime)' => date('Y-m-d', strtotime($selected_date)),
'id_users_provider' => $provider_id
'DATE(start_datetime)' => date('Y-m-d', strtotime($selected_date)),
'id_users_provider' => $provider_id
);
$reserved_appointments = $this->appointments_model->get_batch($where_clause);
@ -474,8 +470,7 @@ class Appointments extends CI_Controller {
// Find the empty spaces on the plan. The first split between the plan is due to
// a break (if exist). After that every reserved appointment is considered to be
// a taken space in the plan.
$selected_date_working_plan = $working_plan[strtolower(date('l',
strtotime($selected_date)))];
$selected_date_working_plan = $working_plan[strtolower(date('l', strtotime($selected_date)))];
$available_periods_with_breaks = array();
if (isset($selected_date_working_plan['breaks'])) {
@ -486,22 +481,22 @@ class Appointments extends CI_Controller {
if (count($available_periods_with_breaks) === 0) {
$start_hour = $selected_date_working_plan['start'];
$end_hour = $break['start'];
$end_hour = $break['start'];
} else {
$start_hour = $selected_date_working_plan['breaks'][$last_break_index]['end'];
$end_hour = $break['start'];
$end_hour = $break['start'];
}
$available_periods_with_breaks[] = array(
'start' => $start_hour,
'end' => $end_hour
'end' => $end_hour
);
}
// Add the period from the last break to the end of the day.
$available_periods_with_breaks[] = array(
'start' => $selected_date_working_plan['breaks'][$index]['end'],
'end' => $selected_date_working_plan['end']
'end' => $selected_date_working_plan['end']
);
}
@ -512,19 +507,19 @@ class Appointments extends CI_Controller {
foreach($available_periods_with_breaks as $period) {
foreach($reserved_appointments as $index=>$excluded_appointment) {
$appointment_start = date('H:i', strtotime($excluded_appointment['start_datetime']));
$appointment_end = date('H:i', strtotime($excluded_appointment['end_datetime']));
$period_start = date('H:i', strtotime($period['start']));
$period_end = date('H:i', strtotime($period['end']));
foreach($reserved_appointments as $index=>$appointment) {
$appointment_start = date('H:i', strtotime($appointment['start_datetime']));
$appointment_end = date('H:i', strtotime($appointment['end_datetime']));
$period_start = date('H:i', strtotime($period['start']));
$period_end = date('H:i', strtotime($period['end']));
if ($period_start < $appointment_start && $period_end > $appointment_end) {
if ($period_start <= $appointment_start && $period_end >= $appointment_end) {
// We need to check whether another appointment fits in the current
// time period. If this happens, then we need to consider the whole
// appointment time as one, because the provider will not be available.
foreach ($reserved_appointments as $tmp_appointment) {
$appt_start = date('H:i', strtotime($tmp_appointment['start_datetime']));
$appt_end = date('H:i', strtotime($tmp_appointment['end_datetime']));
$appt_start = date('H:i', strtotime($tmp_appointment['start_datetime']));
$appt_end = date('H:i', strtotime($tmp_appointment['end_datetime']));
if ($period_start < $appt_start && $period_end > $appt_end) {
if ($appointment_start > $appt_start) {

View file

@ -8,23 +8,34 @@ class Backend extends CI_Controller {
* view this page which displays a calendar with the events of the selected
* provider or service. If a user has more priviledges he will see more menus
* at the top of the page.
*
* @param string $appointment_hash If given, the appointment edit dialog will
* appear when the page loads.
*/
public function index() {
public function index($appointment_hash = '') {
// @task Require user to be logged in the application.
$this->load->model('appointments_model');
$this->load->model('providers_model');
$this->load->model('services_model');
$this->load->model('settings_model');
$view_data['base_url'] = $this->config->item('base_url');
$view_data['book_advance_timeout'] = $this->settings_model->get_setting('book_advance_timeout');
$view_data['company_name'] = $this->settings_model->get_setting('company_name');
$view_data['available_providers'] = $this->providers_model->get_available_providers();
$view_data['available_services'] = $this->services_model->get_available_services();
$view['base_url'] = $this->config->item('base_url');
$view['book_advance_timeout'] = $this->settings_model->get_setting('book_advance_timeout');
$view['company_name'] = $this->settings_model->get_setting('company_name');
$view['available_providers'] = $this->providers_model->get_available_providers();
$view['available_services'] = $this->services_model->get_available_services();
$this->load->view('backend/header', $view_data);
$this->load->view('backend/calendar', $view_data);
$this->load->view('backend/footer', $view_data);
if ($appointment_hash != '') {
$results = $this->appointments_model->get_batch(array('hash' => $appointment_hash));
$view['edit_appointment'] = $results[0]; // This will display the appointment edit dialog on page load.
} else {
$view['edit_appointment'] = NULL;
}
$this->load->view('backend/header', $view);
$this->load->view('backend/calendar', $view);
$this->load->view('backend/footer', $view);
}
/**
@ -40,15 +51,15 @@ class Backend extends CI_Controller {
$this->load->model('services_model');
$this->load->model('settings_model');
$view_data['base_url'] = $this->config->item('base_url');
$view_data['company_name'] = $this->settings_model->get_setting('company_name');
$view_data['customers'] = $this->customers_model->get_batch();
$view_data['available_providers'] = $this->providers_model->get_available_providers();
$view_data['available_services'] = $this->services_model->get_available_services();
$view['base_url'] = $this->config->item('base_url');
$view['company_name'] = $this->settings_model->get_setting('company_name');
$view['customers'] = $this->customers_model->get_batch();
$view['available_providers'] = $this->providers_model->get_available_providers();
$view['available_services'] = $this->services_model->get_available_services();
$this->load->view('backend/header', $view_data);
$this->load->view('backend/customers', $view_data);
$this->load->view('backend/footer', $view_data);
$this->load->view('backend/header', $view);
$this->load->view('backend/customers', $view);
$this->load->view('backend/footer', $view);
}
public function services() {

View file

@ -7,12 +7,11 @@ class Backend_api extends CI_Controller {
/**
* [AJAX] Get the registered appointments for the given date period and record.
*
* This method returns the database appointments for the user selected date
* period and record type (provider or service).
* This method returns the database appointments and unavailable periods for the
* user selected date period and record type (provider or service).
*
* @param {numeric} $_POST['record_id'] Selected record id.
* @param {string} $_POST['filter_type'] Could be either FILTER_TYPE_PROVIDER
* or FILTER_TYPE_SERVICE.
* @param {string} $_POST['filter_type'] Could be either FILTER_TYPE_PROVIDER or FILTER_TYPE_SERVICE.
* @param {string} $_POST['start_date'] The user selected start date.
* @param {string} $_POST['end_date'] The user selected end date.
*/
@ -29,23 +28,35 @@ class Backend_api extends CI_Controller {
$where_id = 'id_services';
}
// Get appointments
$where_clause = array(
$where_id => $_POST['record_id'],
'start_datetime >=' => $_POST['start_date'],
'end_datetime <=' => $_POST['end_date']
'end_datetime <=' => $_POST['end_date'],
'is_unavailable' => FALSE
);
$response['appointments'] = $this->appointments_model->get_batch($where_clause);
$appointments = $this->appointments_model->get_batch($where_clause);
foreach($appointments as &$appointment) {
if ($appointment['is_unavailable'] == FALSE) {
$appointment['provider'] = $this->providers_model->get_row($appointment['id_users_provider']);
$appointment['service'] = $this->services_model->get_row($appointment['id_services']);
$appointment['customer'] = $this->customers_model->get_row($appointment['id_users_customer']);
}
foreach($response['appointments'] as &$appointment) {
$appointment['provider'] = $this->providers_model->get_row($appointment['id_users_provider']);
$appointment['service'] = $this->services_model->get_row($appointment['id_services']);
$appointment['customer'] = $this->customers_model->get_row($appointment['id_users_customer']);
}
echo json_encode($appointments);
// Get unavailable periods (only for provider).
if ($_POST['filter_type'] == FILTER_TYPE_PROVIDER) {
$where_clause = array(
$where_id => $_POST['record_id'],
'start_datetime >=' => $_POST['start_date'],
'end_datetime <=' => $_POST['end_date'],
'is_unavailable' => TRUE
);
$response['unavailables'] = $this->appointments_model->get_batch($where_clause);
}
echo json_encode($response);
} catch(Exception $exc) {
echo json_encode(array(
@ -205,10 +216,10 @@ class Backend_api extends CI_Controller {
$this->load->model('services_model');
$this->load->model('settings_model');
$appointment_data = $this->appointments_model->get_row($_POST['appointment_id']);
$provider_data = $this->providers_model->get_row($appointment_data['id_users_provider']);
$customer_data = $this->customers_model->get_row($appointment_data['id_users_customer']);
$service_data = $this->services_model->get_row($appointment_data['id_services']);
$appointment = $this->appointments_model->get_row($_POST['appointment_id']);
$provider = $this->providers_model->get_row($appointment['id_users_provider']);
$customer = $this->customers_model->get_row($appointment['id_users_customer']);
$service = $this->services_model->get_row($appointment['id_services']);
$company_settings = array(
'company_name' => $this->settings_model->get_setting('company_name'),
@ -220,16 +231,16 @@ class Backend_api extends CI_Controller {
$this->appointments_model->delete($_POST['appointment_id']);
// :: SYNC DELETE WITH GOOGLE CALENDAR
if ($appointment_data['id_google_calendar'] != NULL) {
if ($appointment['id_google_calendar'] != NULL) {
try {
$google_sync = $this->providers_model->get_setting('google_sync', $provider_data['id']);
$google_sync = $this->providers_model->get_setting('google_sync', $provider['id']);
if ($google_sync == TRUE) {
$google_token = json_decode($this->providers_model
->get_setting('google_token', $provider_data['id']));
->get_setting('google_token', $provider['id']));
$this->load->library('Google_Sync');
$this->google_sync->refresh_token($google_token->refresh_token);
$this->google_sync->delete_appointment($appointment_data['id_google_calendar']);
$this->google_sync->delete_appointment($appointment['id_google_calendar']);
}
} catch(Exception $exc) {
$warnings[] = exceptionToJavascript($exc);
@ -239,11 +250,11 @@ class Backend_api extends CI_Controller {
// :: SEND NOTIFICATION EMAILS TO PROVIDER AND CUSTOMER
try {
$this->load->library('Notifications');
$this->notifications->send_delete_appointment($appointment_data, $provider_data,
$service_data, $customer_data, $company_settings, $provider_data['email'],
$this->notifications->send_delete_appointment($appointment, $provider,
$service, $customer, $company_settings, $provider['email'],
$_POST['delete_reason']);
$this->notifications->send_delete_appointment($appointment_data, $provider_data,
$service_data, $customer_data, $company_settings, $customer_data['email'],
$this->notifications->send_delete_appointment($appointment, $provider,
$service, $customer, $company_settings, $customer['email'],
$_POST['delete_reason']);
} catch(Exception $exc) {
$warnings[] = exceptionToJavascript($exc);
@ -334,7 +345,8 @@ class Backend_api extends CI_Controller {
// Add appointment
$unavailable = json_decode($_POST['unavailable'], true);
$this->appointments_model->add_unavailable($unavailable);
$unavailable['id'] = $this->appointments_model->add_unavailable($unavailable);
$unavailable = $this->appointments_model->get_row($unavailable['id']);
// Google Sync
try {
@ -348,11 +360,13 @@ class Backend_api extends CI_Controller {
$this->load->library('google_sync');
$this->google_sync->refresh_token($google_token->refresh_token);
// @task Sync with gcal.
$google_event = $this->google_sync->add_unavailable($unavailable);
$unavailable['id_google_calendar'] = $google_event->id;
$this->appointments_model->add_unavailable($unavailable);
if ($unavailable['id_google_calendar'] == NULL) {
$google_event = $this->google_sync->add_unavailable($unavailable);
$unavailable['id_google_calendar'] = $google_event->id;
$this->appointments_model->add_unavailable($unavailable);
} else {
$google_event = $this->google_sync->update_unavailable($unavailable);
}
}
} catch(Exception $exc) {
$warnings[] = $exc;
@ -380,9 +394,35 @@ class Backend_api extends CI_Controller {
*/
public function ajax_delete_unavailable() {
try {
// Delete unavailable
$this->load->model('appointments_model');
$this->load->model('providers_model');
$unavailable = $this->appointments_model->get_row($_POST['unavailable_id']);
$provider = $this->providers_model->get_row($unavailable['id_users_provider']);
// Delete unavailable
$this->appointments_model->delete_unavailable($unavailable['id']);
// Google Sync
try {
$google_sync = $this->providers_model->get_setting('google_sync', $provider['id']);
if ($google_sync == TRUE) {
$google_token = json_decode($this->providers_model->get_setting('google_token', $provider['id']));
$this->load->library('google_sync');
$this->google_sync->refresh_token($google_token->refresh_token);
$this->google_sync->delete_unavailable($unavailable['id_google_calendar']);
}
} catch(Exception $exc) {
$warnings[] = $exc;
}
if (isset($warnings)) {
echo json_encode(array(
'warnings' => $warnings
));
} else {
echo json_encode('SUCCESS');
}
} catch(Exception $exc) {
echo json_encode(array(

View file

@ -63,6 +63,125 @@ class Google extends CI_Controller {
echo '<h1>Authorization Failed!</h1>';
}
}
/**
* Complete synchronization of appointments between Google Calendar and Easy!Appointments.
*
* This method will completely sync the appointments of a provider with his Google Calendar
* account. The sync period needs to be relatively small, because a lot of API calls might
* be necessary and this will lead to consuming the Google limit for the Calendar API usage.
*
* @param numeric $provider_id Provider record to be synced.
*
* @task This method must be executed only by the system and noone else outside. It is a big security issue.
*/
public function sync($provider_id = NULL) {
try {
if ($provider_id === NULL) {
throw new Exception('Provider id not specified.');
}
$this->load->model('appointments_model');
$this->load->model('providers_model');
$this->load->model('services_model');
$this->load->model('customers_model');
$this->load->model('settings_model');
$provider = $this->providers_model->get_row($provider_id);
// Check whether the selected provider has google sync enabled.
$google_sync = $this->providers_model->get_setting('google_sync', $provider['id']);
if (!$google_sync) {
throw new Exception('The selected provider has not the google synchronization '
. 'setting enabled.');
}
$google_token = json_decode($this->providers_model->get_setting('google_token', $provider['id']));
$this->load->library('google_sync');
$this->google_sync->refresh_token($google_token->refresh_token);
// Fetch provider's appointments that belong to the sync time period.
$sync_past_days = $this->providers_model->get_setting('sync_past_days', $provider['id']);
$sync_future_days = $this->providers_model->get_setting('sync_future_days', $provider['id']);
$start = strtotime('-' . $sync_past_days . ' days', strtotime(date('Y-m-d')));
$end = strtotime('+' . $sync_future_days . ' days', strtotime(date('Y-m-d')));
$where_clause = array(
'start_datetime >=' => date('Y-m-d H:i:s', $start),
'end_datetime <=' => date('Y-m-d H:i:s', $end),
'id_users_provider' => $provider['id'],
'is_unavailable' => FALSE
);
$appointments = $this->appointments_model->get_batch($where_clause);
$company_settings = array(
'company_name' => $this->settings_model->get_setting('company_name'),
'company_link' => $this->settings_model->get_setting('company_link'),
'company_email' => $this->settings_model->get_setting('company_email')
);
// Sync each appointment with Google Calendar by following the project's sync
// protocol (see documentation).
foreach($appointments as $appointment) {
$service = $this->services_model->get_row($appointment['id_services']);
$customer = $this->customers_model->get_row($appointment['id_users_customer']);
// :: APPOINTMENT WITH NO GCAL_ID -> ADD TO GCAL
if ($appointment['id_google_calendar'] == NULL) {
$google_event = $this->google_sync->add_appointment($appointment, $provider,
$service, $customer, $company_settings);
$appointment['id_google_calendar'] = $google_event->id;
$this->appointments_model->add($appointment); // save gcal id
}
// :: SYNCED APPOINTMENT NOT FOUND ON GCAL -> DELETE E!A RECORD
if ($appointment['id_google_calendar'] != NULL) {
try {
$google_event = $this->google_sync->get_event($appointment['id_google_calendar']);
} catch(Exception $exc) {
$this->appointments_model->delete($appointment['id']);
$appointment['id_google_calendar'] = NULL; // Do not proceed with the rest sync actions.
}
}
// :: SYNCED APPOINTMENT DIFFERENT FROM GCAL EVENT -> UPDATE E!A RECORD
if ($appointment['id_google_calendar'] != NULL) {
$is_different = FALSE;
$appt_start = strtotime($appointment['start_datetime']);
$appt_end = strtotime($appointment['end_datetime']);
$event_start = strtotime($google_event->getStart()->getDateTime());
$event_end = strtotime($google_event->getEnd()->getDateTime());
if ($appt_start != $event_start
|| $appt_end != $event_end) {
$is_different = TRUE;
}
if ($is_different) {
$appointment['start_datetime'] = date('Y-m-d H:i:s', $event_start);
$appointment['end_datetime'] = date('Y-m-d H:i:s', $event_end);
$this->appointments_model->add($appointment);
}
}
// @task :: GCAL EVENT NOT FOUND ON E!A -> ADD EVENT TO E!A
}
// @task Sync unavailable periods with Google Calendar
echo json_encode('SUCCESS');
} catch(Exception $exc) {
echo json_encode(array(
'exceptions' => array($exc)
));
}
}
}
/* End of file google.php */

View file

@ -82,6 +82,7 @@ class Unit_tests_appointments_model extends CI_Driver {
unset($db_data['hash']);
unset($db_data['book_datetime']);
unset($db_data['id_google_calendar']);
unset($db_data['is_unavailable']);
$this->CI->unit->run($appointment, $db_data, 'Test if add() appointment (insert '
. 'operation) has successfully inserted a record.');
@ -438,6 +439,7 @@ class Unit_tests_appointments_model extends CI_Driver {
$db_data = $this->CI->appointments_model->get_row($appointment['id']);
unset($db_data['book_datetime']);
unset($db_data['id_google_calendar']);
unset($db_data['is_unavailable']);
// Check if this is the record we seek.
$this->CI->unit->run($db_data, $appointment, 'Test get_row() method.');

View file

@ -160,8 +160,6 @@ class Google_Sync {
$event = $this->service->events->get('primary', $appointment['id_google_calendar']);
// Convert event to object
$event->setSummary($service['name']);
$event->setLocation($company_settings['company_name']);
@ -203,8 +201,76 @@ class Google_Sync {
$this->service->events->delete('primary', $google_calendar_id);
}
public function add_unavailble($unavailable) {
// @task Implement
/**
* Add unavailable period event to Google Calendar.
*
* @param array $unavailable Contains unavailable period's data.
* @return Google_Event Returns the google event's object.
*/
public function add_unavailable($unavailable) {
$this->CI->load->helper('general');
$event = new Google_Event();
$event->setSummary('Unavailalbe');
$event->setDescription($unavailable['notes']);
$start = new Google_EventDateTime();
$start->setDateTime(date3339(strtotime($unavailable['start_datetime'])));
$event->setStart($start);
$end = new Google_EventDateTime();
$end->setDateTime(date3339(strtotime($unavailable['end_datetime'])));
$event->setEnd($end);
// Add the new event to the "primary" calendar.
$created_event = $this->service->events->insert('primary', $event);
return $created_event;
}
/**
* Update Google Calendar unavailable period event.
*
* @param array $unavailable Contains the unavailable period data.
* @return Google_Event Returns the Google_Event object.
*/
public function update_unavailable($unavailable) {
$this->CI->load->helper('general');
$event = $this->service->events->get('primary', $unavailable['id_google_calendar']);
$event->setDescription($unavailable['notes']);
$start = new Google_EventDateTime();
$start->setDateTime(date3339(strtotime($unavailable['start_datetime'])));
$event->setStart($start);
$end = new Google_EventDateTime();
$end->setDateTime(date3339(strtotime($unavailable['end_datetime'])));
$event->setEnd($end);
$updated_event = $this->service->events->update('primary', $event->getId(), $event);
return $updated_event;
}
/**
* Delete unavailable period event from Google Calendar.
*
* @param string $google_calendar_id Google Calendar event id to be deleted.
*/
public function delete_unavailable($google_calendar_id) {
$this->service->events->delete('primary', $google_calendar_id);
}
/**
* Get an event object from gcal
*
* @param string $google_calendar_id Id of the google calendar event
* @return Google_Event Returns the google event object.
*/
public function get_event($google_calendar_id) {
return $this->service->events->get('primary', $google_calendar_id);
}
}

View file

@ -360,7 +360,7 @@ class Appointments_Model extends CI_Model {
*/
public function delete_unavailable($unavailable_id) {
if (!is_numeric($unavailable_id)) {
throw new Exception('Invalid argument type $appointment_id (value:"' .
throw new Exception('Invalid argument type $unavailable_id (value:"' .
$unavailable_id . '")');
}

View file

@ -129,7 +129,7 @@ class Customers_Model extends CI_Model {
*/
public function find_record_id($customer) {
if (!isset($customer['email'])) {
throw new InvalidArgumentException('Customer\'s email was not provided : '
throw new Exception('Customer\'s email was not provided : '
. print_r($customer, TRUE));
}
@ -244,7 +244,7 @@ class Customers_Model extends CI_Model {
}
if (!is_string($field_name)) {
throw new IException('$field_name argument is not a string : '
throw new Exception('$field_name argument is not a string : '
. $field_name);
}

View file

@ -15,7 +15,8 @@
'availableProviders' : <?php echo json_encode($available_providers); ?>,
'availableServices' : <?php echo json_encode($available_services); ?>,
'baseUrl' : <?php echo '"' . $base_url . '"'; ?>,
'bookAdvanceTimeout' : <?php echo $book_advance_timeout; ?>
'bookAdvanceTimeout' : <?php echo $book_advance_timeout; ?>,
'editAppointment' : <?php echo json_encode($edit_appointment); ?>
};
$(document).ready(function() {

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Easy!Appointments Backend</title>
<title>Easy!Appointments Backend | <?php echo $company_name; ?></title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="icon" type="image/x-icon"

View file

@ -53,7 +53,7 @@
<td style="padding: 3px;">$customer_address</td>
</tr>
</table>
<h2>Appointment Link</h2>
<a href="$appointment_link" style="width: 600px;">$appointment_link</a>
</div>

View file

@ -51,6 +51,7 @@ root {
font-size: 24px; border: none; border-radius: 0; font-weight: bold; color: #333;
text-shadow: 0px 1px 0px #FFF;}
#calendar-page #calendar .fc-break { background-image: url('../images/break.jpg'); }
#calendar-page #calendar .fc-custom { background-image: url('../images/custom.jpg'); }
/* BACKEND CUSTOMERS PAGE
-------------------------------------------------------------------- */

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View file

@ -28,6 +28,8 @@ var BackendCalendar = {
'height': BackendCalendar.getCalendarHeight(),
'editable': true,
'slotMinutes': 30,
'axisFormat': 'HH:mm',
'timeFormat': 'HH:mm{ - HH:mm}',
'columnFormat': {
'month': 'ddd',
'week': 'ddd d/M',
@ -85,6 +87,11 @@ var BackendCalendar = {
BackendCalendar.bindEventHandlers();
$('#select-filter-item').trigger('change');
}
// :: DISPLAY EDIT DIALOG IF APPOINTMENT HASH IS PROVIDED
if (GlobalVariables.editAppointment != null) {
// @task Display appointment edit dialog to user.
}
},
/**
@ -160,38 +167,58 @@ var BackendCalendar = {
$(document).on('click', '.edit-popover', function() {
$(this).parents().eq(2).remove(); // Hide the popover
var appointment = BackendCalendar.lastFocusedEventData.data;
var $dialog = $('#manage-appointment');
var $dialog;
BackendCalendar.resetAppointmentDialog();
if (BackendCalendar.lastFocusedEventData.data.is_unavailable == false) {
var appointment = BackendCalendar.lastFocusedEventData.data;
$dialog = $('#manage-appointment');
BackendCalendar.resetAppointmentDialog();
// :: APPLY APPOINTMENT DATA AND SHOW TO MODAL DIALOG
$dialog.find('.modal-header h3').text('Edit Appointment');
$dialog.find('#appointment-id').val(appointment['id']);
$dialog.find('#select-service').val(appointment['id_services']);
$dialog.find('#select-provider').val(appointment['id_users_provider']);
// Set the start and end datetime of the appointment.\
var startDatetime = Date.parseExact(appointment['start_datetime'],
'yyyy-MM-dd HH:mm:ss').toString('dd/MM/yyyy HH:mm');
$dialog.find('#start-datetime').val(startDatetime);
var endDatetime = Date.parseExact(appointment['end_datetime'],
'yyyy-MM-dd HH:mm:ss').toString('dd/MM/yyyy HH:mm');
$dialog.find('#end-datetime').val(endDatetime);
var customer = appointment['customer'];
$dialog.find('#customer-id').val(appointment['id_users_customer']);
$dialog.find('#first-name').val(customer['first_name']);
$dialog.find('#last-name').val(customer['last_name']);
$dialog.find('#email').val(customer['email']);
$dialog.find('#phone-number').val(customer['phone_number']);
$dialog.find('#address').val(customer['address']);
$dialog.find('#city').val(customer['city']);
$dialog.find('#zip-code').val(customer['zip_code']);
$dialog.find('#notes').val(appointment['notes']);
} else {
var unavailable = BackendCalendar.lastFocusedEventData.data;
// Replace string date values with actual date objects.
unavailable.start_datetime = GeneralFunctions.clone(BackendCalendar.lastFocusedEventData.start);
unavailable.end_datetime = GeneralFunctions.clone(BackendCalendar.lastFocusedEventData.end);
$dialog = $('#manage-unavailable');
BackendCalendar.resetUnavailableDialog();
// :: APPLY UNAVAILABLE DATA TO DIALOG
$dialog.find('.modal-header h3').text('Edit Unavailable Period');
$dialog.find('#unavailable-id').val(unavailable.id);
$dialog.find('#unavailable-start').val(unavailable.start_datetime.toString('dd/MM/yyyy HH:mm'));
$dialog.find('#unavailable-end').val(unavailable.end_datetime.toString('dd/MM/yyyy HH:mm'));
$dialog.find('#unavailable-notes').val(unavailable.notes);
}
// :: APPLY APPOINTMENT DATA AND SHOW TO MODAL DIALOG
$dialog.find('.modal-header h3').text('Edit Appointment');
$dialog.find('#appointment-id').val(appointment['id']);
$dialog.find('#select-service').val(appointment['id_services']);
$dialog.find('#select-provider').val(appointment['id_users_provider']);
// Set the start and end datetime of the appointment.\
var startDatetime = Date.parseExact(appointment['start_datetime'],
'yyyy-MM-dd HH:mm:ss').toString('dd/MM/yyyy HH:mm');
$dialog.find('#start-datetime').val(startDatetime);
var endDatetime = Date.parseExact(appointment['end_datetime'],
'yyyy-MM-dd HH:mm:ss').toString('dd/MM/yyyy HH:mm');
$dialog.find('#end-datetime').val(endDatetime);
var customer = appointment['customer'];
$dialog.find('#customer-id').val(appointment['id_users_customer']);
$dialog.find('#first-name').val(customer['first_name']);
$dialog.find('#last-name').val(customer['last_name']);
$dialog.find('#email').val(customer['email']);
$dialog.find('#phone-number').val(customer['phone_number']);
$dialog.find('#address').val(customer['address']);
$dialog.find('#city').val(customer['city']);
$dialog.find('#zip-code').val(customer['zip_code']);
$dialog.find('#notes').val(appointment['notes']);
// :: DISPLAY THE MANAGE APPOINTMENTS MODAL DIALOG
// :: DIAPLY EDIT DIALOG
$dialog.modal('show');
});
@ -205,48 +232,81 @@ var BackendCalendar = {
$(document).on('click', '.delete-popover', function() {
$(this).parents().eq(2).remove(); // Hide the popover
var messageButtons = {
'OK': function() {
var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_delete_appointment';
var postData = {
'appointment_id' : BackendCalendar.lastFocusedEventData.data['id'],
'delete_reason': $('#delete-reason').val()
};
$.post(postUrl, postData, function(response) {
/////////////////////////////////////////////////////////
console.log('Delete Appointment Response :', response);
/////////////////////////////////////////////////////////
if (BackendCalendar.lastFocusedEventData.data.is_unavailable == false) {
var messageButtons = {
'OK': function() {
var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_delete_appointment';
var postData = {
'appointment_id' : BackendCalendar.lastFocusedEventData.data['id'],
'delete_reason': $('#delete-reason').val()
};
$.post(postUrl, postData, function(response) {
/////////////////////////////////////////////////////////
console.log('Delete Appointment Response :', response);
/////////////////////////////////////////////////////////
$('#message_box').dialog('close');
if (response.exceptions) {
response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
return;
}
if (response.warnings) {
response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
}
// Refresh calendar event items.
$('#select-filter-item').trigger('change');
}, 'json');
},
'Cancel': function() {
$('#message_box').dialog('close');
if (response.exceptions) {
response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
return;
}
if (response.warnings) {
response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
}
// Refresh calendar event items.
$('#select-filter-item').trigger('change');
}, 'json');
},
'Cancel': function() {
}
};
GeneralFunctions.displayMessageBox('Delete Appointment', 'Please take a minute '
+ 'to write the reason you are deleting the appointment:', messageButtons);
$('#message_box').append('<textarea id="delete-reason"></textarea>');
$('#delete-reason').css('width', '320px');
} else {
// Do not display confirmation promt.
var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_delete_unavailable';
var postData = {
'unavailable_id' : BackendCalendar.lastFocusedEventData.data.id
};
$.post(postUrl, postData, function(response) {
/////////////////////////////////////////////////////////
console.log('Delete Unavailable Response :', response);
/////////////////////////////////////////////////////////
$('#message_box').dialog('close');
}
};
GeneralFunctions.displayMessageBox('Delete Appointment', 'Please take a minute '
+ 'to write the reason you are deleting the appointment:', messageButtons);
$('#message_box').append('<textarea id="delete-reason"></textarea>');
$('#delete-reason').css('width', '320px');
if (response.exceptions) {
response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
return;
}
if (response.warnings) {
response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
}
// Refresh calendar event items.
$('#select-filter-item').trigger('change');
}, 'json');
}
});
/**
@ -353,6 +413,11 @@ var BackendCalendar = {
successCallback, errorCallback);
});
/**
* Event: Manage Unavailable Dialog Save Button "Click"
*
* Stores the unavailable period changes or inserts a new record.
*/
$('#manage-unavailable #save-unavailable').click(function() {
var $dialog = $('#manage-unavailable');
@ -365,6 +430,7 @@ var BackendCalendar = {
'<div class="alert alert-error">' +
'Start date value is bigger than end date!' +
'</div>');
$dialog.find('.modal-message').show();
return;
}
@ -376,6 +442,11 @@ var BackendCalendar = {
'id_users_provider': $('#select-filter-item').val() // curr provider
};
if ($dialog.find('#unavailable-id').val() !== '') {
// Set the id value, only if we are editing an appointment.
unavailable.id = $dialog.find('#unavailable-id').val();
}
var successCallback = function(response) {
///////////////////////////////////////////////////////////////////
console.log('Save Unavailable Time Period Response:', response);
@ -385,6 +456,12 @@ var BackendCalendar = {
response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
$dialog.find('.modal-header').append(
'<br><div class="alert alert-error">' +
'Unexpected issues occured!' +
'</div>');
return;
}
@ -393,8 +470,20 @@ var BackendCalendar = {
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
}
$('#manage-unavailable').modal('hide');
// Display success message to the user.
$dialog.find('.modal-header').append(
'<br><div class="alert alert-success">' +
'Appointment saved successfully!' +
'</div>');
// Close the modal dialog and refresh the calendar appointments
// after one second.
setTimeout(function() {
$dialog.find('.alert').remove();
$dialog.modal('hide');
$('#select-filter-item').trigger('change');
}, 2000);
};
var errorCallback = function(jqXHR, textStatus, errorThrown) {
@ -404,11 +493,21 @@ var BackendCalendar = {
GeneralFunctions.displayMessageBox('Communication Error', 'Unfortunately ' +
'the operation could not complete due to server communication errors.');
$dialog.find('.modal-header').append(
'<br><div class="alert alert-error">' +
'A server communication error occured, please try again.' +
'</div>');
};
BackendCalendar.saveUnavalable(unavailable, successCallback, errorCallback);
BackendCalendar.saveUnavailable(unavailable, successCallback, errorCallback);
});
/**
* Event: Manage Unavailable Dialog Cancel Button "Click"
*
* Closes the dialog without saveing any changes to the database.
*/
$('#manage-unavailable #cancel-unavailable').click(function() {
$('#manage-unavailable').modal('hide');
});
@ -551,8 +650,9 @@ var BackendCalendar = {
// :: ADD APPOINTMENTS TO CALENDAR
var calendarEvents = [];
var $calendar = $('#calendar');
$.each(response, function(index, appointment){
$.each(response.appointments, function(index, appointment){
var event = {
'id': appointment['id'],
'title': appointment['service']['name'] + ' - '
@ -571,7 +671,8 @@ var BackendCalendar = {
$calendar.fullCalendar('addEventSource', calendarEvents);
// :: ADD PROVIDER'S UNAVAILABLE TIME PERIODS
var calendarView = $('#calendar').fullCalendar('getView').name;
var calendarView = $calendar.fullCalendar('getView').name;
if (filterType === BackendCalendar.FILTER_TYPE_PROVIDER
&& calendarView !== 'month') {
@ -582,11 +683,11 @@ var BackendCalendar = {
switch(calendarView) {
case 'agendaDay':
var selDayName = $('#calendar').fullCalendar('getView')
var selDayName = $calendar.fullCalendar('getView')
.start.toString('dddd').toLowerCase();
// Add unavailable period before work starts.
var calendarDateStart = $('#calendar').fullCalendar('getView').start;
var calendarDateStart = $calendar.fullCalendar('getView').start;
var workDateStart = Date.parseExact(
calendarDateStart.toString('dd/MM/yyyy') + ' '
+ workingPlan[selDayName].start,
@ -602,11 +703,11 @@ var BackendCalendar = {
'editable': false,
'className': 'fc-unavailable'
};
$('#calendar').fullCalendar('renderEvent', unavailablePeriod, true);
$calendar.fullCalendar('renderEvent', unavailablePeriod, false);
}
// Add unavailable period after work ends.
var calendarDateEnd = $('#calendar').fullCalendar('getView').end;
var calendarDateEnd = $calendar.fullCalendar('getView').end;
var workDateEnd = Date.parseExact(
calendarDateStart.toString('dd/MM/yyyy') + ' '
+ workingPlan[selDayName].end,
@ -621,7 +722,7 @@ var BackendCalendar = {
'editable': false,
'className': 'fc-unavailable'
};
$('#calendar').fullCalendar('renderEvent', unavailablePeriod, true);
$calendar.fullCalendar('renderEvent', unavailablePeriod, false);
}
// Add unavailable periods for breaks.
@ -640,16 +741,28 @@ var BackendCalendar = {
'editable': false,
'className': 'fc-unavailable fc-break'
};
$('#calendar').fullCalendar('renderEvent', unavailablePeriod, true);
$calendar.fullCalendar('renderEvent', unavailablePeriod, false);
});
// @task Add custom unavailable periods.
// Add custom unavailable periods.
$.each(response.unavailables, function(index, unavailable) {
var unavailablePeriod = {
'title': 'Unavailable',
'start': Date.parse(unavailable.start_datetime),
'end': Date.parse(unavailable.end_datetime),
'allDay': false,
'color': '#123456',
'editable': true,
'className': 'fc-unavailable fc-custom',
'data': unavailable
};
$calendar.fullCalendar('renderEvent', unavailablePeriod, false);
});
break;
case 'agendaWeek':
var currDateStart = GeneralFunctions.clone($('#calendar').fullCalendar('getView').start);
var currDateStart = GeneralFunctions.clone($calendar.fullCalendar('getView').start);
var currDateEnd = GeneralFunctions.clone(currDateStart).addDays(1);
$.each(workingPlan, function(index, workingDay) {
@ -668,7 +781,7 @@ var BackendCalendar = {
'editable': false,
'className': 'fc-unavailable'
};
$('#calendar').fullCalendar('renderEvent', unavailablePeriod,
$calendar.fullCalendar('renderEvent', unavailablePeriod,
true);
}
@ -685,7 +798,7 @@ var BackendCalendar = {
'editable': false,
'className': 'fc-unavailable fc-brake'
};
$('#calendar').fullCalendar('renderEvent', unavailablePeriod, true);
$calendar.fullCalendar('renderEvent', unavailablePeriod, false);
}
// Add unavailable periods during day breaks.
@ -704,10 +817,26 @@ var BackendCalendar = {
'editable': false,
'className': 'fc-unavailable fc-break'
};
$('#calendar').fullCalendar('renderEvent', unavailablePeriod, true);
$calendar.fullCalendar('renderEvent', unavailablePeriod, false);
});
// @task Add custom unavailable periods.
// Add custom unavailable periods.
$.each(response.unavailables, function(index, unavailable) {
if (currDateStart.toString('dd/MM/yyyy')
=== Date.parse(unavailable.start_datetime).toString('dd/MM/yyyy')) {
var unavailablePeriod = {
'title': 'Unavailable',
'start': Date.parse(unavailable.start_datetime),
'end': Date.parse(unavailable.end_datetime),
'allDay': false,
'color': '#123456',
'editable': true,
'className': 'fc-unavailable fc-custom',
'data': unavailable
};
$calendar.fullCalendar('renderEvent', unavailablePeriod, false);
}
});
currDateStart.addDays(1);
currDateEnd.addDays(1);
@ -778,7 +907,7 @@ var BackendCalendar = {
* @param {function} successCallback The ajax success callback function.
* @param {function} errorCallback The ajax failure callback function.
*/
saveUnavalable: function(unavailable, successCallback, errorCallback) {
saveUnavailable: function(unavailable, successCallback, errorCallback) {
var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_save_unavailable';
var postData = {
@ -809,64 +938,117 @@ var BackendCalendar = {
$('#notification').hide('bind');
}
// :: PREPARE THE APPOINTMENT DATA
var appointment = GeneralFunctions.clone(event.data);
if (event.data.is_unavailable == false) {
// :: PREPARE THE APPOINTMENT DATA
var appointment = GeneralFunctions.clone(event.data);
// Must delete the following because only appointment data should be
// provided to the ajax call.
delete appointment['customer'];
delete appointment['provider'];
delete appointment['service'];
// Must delete the following because only appointment data should be
// provided to the ajax call.
delete appointment['customer'];
delete appointment['provider'];
delete appointment['service'];
appointment['end_datetime'] = Date.parseExact(
appointment['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ minutes: minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
appointment['end_datetime'] = Date.parseExact(
appointment['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ minutes: minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
// :: DEFINE THE SUCCESS CALLBACK FUNCTION
var successCallback = function(response) {
if (response.exceptions) {
response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
return;
}
if (response.warnings) {
// Display warning information to the user.
response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
}
// :: DEFINE THE SUCCESS CALLBACK FUNCTION
var successCallback = function(response) {
if (response.exceptions) {
response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
return;
}
// Display success notification to user.
var undoFunction = function() {
appointment['end_datetime'] = Date.parseExact(
appointment['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ minutes: -minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_save_appointment';
var postData = { 'appointment_data': JSON.stringify(appointment) };
if (response.warnings) {
// Display warning information to the user.
response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
}
$.post(postUrl, postData, function(response) {
$('#notification').hide('blind');
revertFunc();
});
// Display success notification to user.
var undoFunction = function() {
appointment['end_datetime'] = Date.parseExact(
appointment['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ minutes: -minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_save_appointment';
var postData = { 'appointment_data': JSON.stringify(appointment) };
$.post(postUrl, postData, function(response) {
$('#notification').hide('blind');
revertFunc();
});
};
Backend.displayNotification('Appointment updated successfully!', [
{
'label': 'Undo',
'function': undoFunction
}
]);
$('#footer').css('position', 'static'); // Footer position fix.
};
Backend.displayNotification('Appointment updated successfully!', [
{
'label': 'Undo',
'function': undoFunction
}
]);
$('#footer').css('position', 'static'); // Footer position fix.
};
// :: UPDATE APPOINTMENT DATA VIA AJAX CALL
BackendCalendar.saveAppointment(appointment, undefined,
successCallback, undefined);
} else {
// :: UPDATE UNAVAILABLE TIME PERIOD
var unavailable = {
'id': event.data.id,
'start_datetime': event.start.toString('yyyy-MM-dd HH:mm:ss'),
'end_datetime': event.end.toString('yyyy-MM-dd HH:mm:ss'),
'id_users_provider': event.data.id_users_provider
};
// :: UPDATE APPOINTMENT DATA VIA AJAX CALL
BackendCalendar.saveAppointment(appointment, undefined,
successCallback, undefined);
// :: DEFINE THE SUCCESS CALLBACK FUNCTION
var successCallback = function(response) {
if (response.exceptions) {
response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
return;
}
if (response.warnings) {
// Display warning information to the user.
response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
}
// Display success notification to user.
var undoFunction = function() {
unavailable['end_datetime'] = Date.parseExact(
unavailable['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ minutes: -minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_save_unavailable';
var postData = { 'unavailable': JSON.stringify(unavailable) };
$.post(postUrl, postData, function(response) {
$('#notification').hide('blind');
revertFunc();
});
};
Backend.displayNotification('Unavailable time period updated successfully!', [
{
'label': 'Undo',
'function': undoFunction
}
]);
$('#footer').css('position', 'static'); // Footer position fix.
};
BackendCalendar.saveUnavailable(unavailable, successCallback, undefined);
}
},
/**
@ -905,10 +1087,23 @@ var BackendCalendar = {
calendarEventClick: function(event, jsEvent, view) {
$('.popover').remove(); // Close all open popovers.
var html;
var html; // Popover's html code
if ($(jsEvent.target.offsetParent).hasClass('fc-unavailable')
|| $(jsEvent.target).parents().eq(1).hasClass('fc-unavailable')) {
// Depending where the user clicked the event (title or empty space) we
// need to use different selectors to reach the parent element.
var $parent = $(jsEvent.target.offsetParent);
var $altParent = $(jsEvent.target).parents().eq(1);
if ($parent.hasClass('fc-unavailable') || $altParent.hasClass('fc-unavailable')) {
var displayEdit = ($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
? '' : 'hide';
var displayDelete = displayEdit; // Same value at the time.
var notes = '';
if (event.data) { // Only custom unavailable periods have notes.
notes = '<strong>Notes</strong> ' + event.data.notes;
}
html =
'<style type="text/css">'
+ '.popover-content strong {min-width: 80px; display:inline-block;}'
@ -919,10 +1114,12 @@ var BackendCalendar = {
+ '<br>' +
'<strong>End</strong> '
+ event.end.toString('dd/MM/yyyy HH:mm')
+ '<br>' +
+ '<br>'
+ notes
+ '<hr>' +
'<center>' +
'<button class="edit-popover btn btn-primary">Edit</button>' +
'<button class="delete-popover btn btn-danger">Delete</button>' +
'<button class="edit-popover btn btn-primary ' + displayEdit + '">Edit</button>' +
'<button class="delete-popover btn btn-danger ' + displayDelete + '">Delete</button>' +
'<button class="close-popover btn" data-po=' + jsEvent.target + '>Close</button>' +
'</center>';
} else {
@ -966,6 +1163,9 @@ var BackendCalendar = {
BackendCalendar.lastFocusedEventData = event;
$(jsEvent.target).popover('show');
// Fix popover position
if ($('.popover').position().top < 200) $('.popover').css('top', '200px');
},
/**
@ -979,84 +1179,147 @@ var BackendCalendar = {
revertFunc, jsEvent, ui, view) {
if ($('#notification').is(':visible')) {
$('#notification').hide('bind');
}
}
if (event.data.is_unavailable == false) {
// :: PREPARE THE APPOINTMENT DATA
var appointment = GeneralFunctions.clone(event.data);
// Must delete the following because only appointment data should be
// provided to the ajax call.
delete appointment['customer'];
delete appointment['provider'];
delete appointment['service'];
// :: PREPARE THE APPOINTMENT DATA
var appointment = GeneralFunctions.clone(event.data);
appointment['start_datetime'] = Date.parseExact(
appointment['start_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ days: dayDelta, minutes: minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
appointment['end_datetime'] = Date.parseExact(
appointment['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ days: dayDelta, minutes: minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
event.data['start_datetime'] = appointment['start_datetime'];
event.data['end_datetime'] = appointment['end_datetime'];
// :: DEFINE THE SUCCESS CALLBACK FUNCTION
var successCallback = function(response) {
if (response.exceptions) {
response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
return;
}
if (response.warnings) {
// Display warning information to the user.
response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
}
// Define the undo function, if the user needs to reset the last change.
var undoFunction = function() {
appointment['start_datetime'] = Date.parseExact(
appointment['start_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ days: -dayDelta, minutes: -minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
// Must delete the following because only appointment data should be
// provided to the ajax call.
delete appointment['customer'];
delete appointment['provider'];
delete appointment['service'];
appointment['end_datetime'] = Date.parseExact(
appointment['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ days: -dayDelta, minutes: -minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
event.data['start_datetime'] = appointment['start_datetime'];
event.data['end_datetime'] = appointment['end_datetime'];
var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_save_appointment';
var postData = { 'appointment_data': JSON.stringify(appointment) };
appointment['start_datetime'] = Date.parseExact(
appointment['start_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ days: dayDelta, minutes: minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
$.post(postUrl, postData, function(response) {
$('#notification').hide('blind');
revertFunc();
});
appointment['end_datetime'] = Date.parseExact(
appointment['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ days: dayDelta, minutes: minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
event.data['start_datetime'] = appointment['start_datetime'];
event.data['end_datetime'] = appointment['end_datetime'];
// :: DEFINE THE SUCCESS CALLBACK FUNCTION
var successCallback = function(response) {
if (response.exceptions) {
response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
return;
}
if (response.warnings) {
// Display warning information to the user.
response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
}
// Define the undo function, if the user needs to reset the last change.
var undoFunction = function() {
appointment['start_datetime'] = Date.parseExact(
appointment['start_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ days: -dayDelta, minutes: -minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
appointment['end_datetime'] = Date.parseExact(
appointment['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ days: -dayDelta, minutes: -minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
event.data['start_datetime'] = appointment['start_datetime'];
event.data['end_datetime'] = appointment['end_datetime'];
var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_save_appointment';
var postData = { 'appointment_data': JSON.stringify(appointment) };
$.post(postUrl, postData, function(response) {
$('#notification').hide('blind');
revertFunc();
});
};
Backend.displayNotification('Appointment updated successfully!', [
{
'label': 'Undo',
'function': undoFunction
}
]);
$('#footer').css('position', 'static'); // Footer position fix.
};
Backend.displayNotification('Appointment updated successfully!', [
{
'label': 'Undo',
'function': undoFunction
}
]);
// :: UPDATE APPOINTMENT DATA VIA AJAX CALL
BackendCalendar.saveAppointment(appointment, undefined,
successCallback, undefined);
} else {
// :: UPDATE UNAVAILABLE TIME PERIOD
var unavailable = {
'id': event.data.id,
'start_datetime': event.start.toString('yyyy-MM-dd HH:mm:ss'),
'end_datetime': event.end.toString('yyyy-MM-dd HH:mm:ss'),
'id_users_provider': event.data.id_users_provider
}
$('#footer').css('position', 'static'); // Footer position fix.
};
var successCallback = function(response) {
console.log('Drop Unavailable Event Response:', response);
if (response.exceptions) {
reponse.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
return;
}
if (response.warnings) {
reponse.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
}
var undoFunction = function() {
unavailable['start_datetime'] = Date.parseExact(
unavailable['start_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ days: -dayDelta, minutes: -minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
// :: UPDATE APPOINTMENT DATA VIA AJAX CALL
BackendCalendar.saveAppointment(appointment, undefined,
successCallback, undefined);
unavailable['end_datetime'] = Date.parseExact(
unavailable['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
.add({ days: -dayDelta, minutes: -minuteDelta })
.toString('yyyy-MM-dd HH:mm:ss');
event.data['start_datetime'] = unavailable['start_datetime'];
event.data['end_datetime'] = unavailable['end_datetime'];
var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_save_unavailable';
var postData = { 'unavailable': JSON.stringify(unavailable) };
$.post(postUrl, postData, function(response) {
$('#notification').hide('blind');
revertFunc();
});
};
Backend.displayNotification('Unavailable period updated successfully!', [
{
'label': 'Undo',
'function': undoFunction
}
]);
$('#footer').css('position', 'static'); // Footer position fix.
};
BackendCalendar.saveUnavailable(unavailable, successCallback, undefined);
}
},
/**
@ -1165,14 +1428,12 @@ var BackendCalendar = {
.addMinutes(serviceDuration).toString('dd/MM/yyyy HH:mm');
$dialog.find('#start-datetime').datetimepicker({
'dateFormat': 'dd/mm/yy',
'minDate': 0
'dateFormat': 'dd/mm/yy'
});
$dialog.find('#start-datetime').val(startDatetime);
$dialog.find('#end-datetime').datetimepicker({
'dateFormat': 'dd/mm/yy',
'minDate': 0
'dateFormat': 'dd/mm/yy'
});
$dialog.find('#end-datetime').val(endDatetime);
},
@ -1228,14 +1489,12 @@ var BackendCalendar = {
var end = new Date().addHours(1).toString('dd/MM/yyyy HH:mm');
$dialog.find('#unavailable-start').datetimepicker({
'dateFormat': 'dd/mm/yy',
'minDate': 0
'dateFormat': 'dd/mm/yy'
});
$dialog.find('#unavailable-start').val(start);
$dialog.find('#unavailable-end').datetimepicker({
'dateFormat': 'dd/mm/yy',
'minDate': 0
'dateFormat': 'dd/mm/yy'
});
$dialog.find('#unavailable-end').val(end);

View file

@ -345,7 +345,7 @@ var FrontendBook = {
FrontendBook.updateConfirmFrame();
} else {
$('#available-hours').text('There are no available appointment'
$('#available-hours').text('There are no available appointment '
+ 'hours for the selected date. Please choose another date.');
}
}, 'json');