Implelements the js part of the appointment booking page.
+
Implements the js part of the appointment booking page.
@@ -49,9 +49,9 @@
- This class implements the book appointment page functionality.
-Once the initialize() method is called the page is fully functional
-and can serve the appointment booking process.
+ This class implements the book appointment page functionality. Once
+the initialize() method is called the page is fully functional and
+can serve the appointment booking process.
@@ -123,12 +123,266 @@ and can serve the appointment booking process.
+
@@ -138,8 +392,8 @@ and can serve the appointment booking process.
- This method binds the necessary event handlers
-for the book appointments page.
+ This method binds the necessary event handlers for the book
+appointments page.
@@ -172,7 +426,7 @@ for the book appointments page.
@@ -711,7 +990,7 @@ It only checks for empty fields by the time.
diff --git a/doc/code-docs/js/book_appointment.js.html b/doc/code-docs/js/book_appointment.js.html
index 9c4a0a73..6bad074e 100644
--- a/doc/code-docs/js/book_appointment.js.html
+++ b/doc/code-docs/js/book_appointment.js.html
@@ -26,24 +26,40 @@
/**
- * This class implements the book appointment page functionality.
- * Once the initialize() method is called the page is fully functional
- * and can serve the appointment booking process.
+ * This class implements the book appointment page functionality. Once
+ * the initialize() method is called the page is fully functional and
+ * can serve the appointment booking process.
*
- * @class Implelements the js part of the appointment booking page.
+ * @class Implements the js part of the appointment booking page.
*/
var bookAppointment = {
+ /**
+ * Determines the functionality of the page.
+ *
+ * @type Boolean
+ */
+ manageMode : false,
+
/**
* This method initializes the book appointment page.
*
- * @param {bool} bindEventHandlers (OPTIONAL) Determines wether
+ * @param {bool} bindEventHandlers (OPTIONAL) Determines whether
* the default event handlers will be binded to the dom elements.
+ * @param {bool} manageMode (OPTIONAL) Determines whether the customer
+ * is going to make changes to an existing appointment rather than
+ * booking a new one.
*/
- initialize : function(bindEventHandlers) {
- if (bindEventHandlers == undefined) {
- bindEventHandlers = true; // Default value
+ initialize : function(bindEventHandlers, manageMode) {
+ if (bindEventHandlers === undefined) {
+ bindEventHandlers = true; // Default Value
}
+ if (manageMode === undefined) {
+ manageMode = false; // Default Value
+ }
+
+ bookAppointment.manageMode = manageMode;
+
// Initialize page's components (tooltips, datepickers etc).
$('.book-step').qtip({
position: {
@@ -61,7 +77,7 @@ var bookAppointment = {
defaultDate : Date.today(),
onSelect : function(dateText, instance) {
bookAppointment.getAvailableHours(dateText);
- bookAppointment.updateConfirmData();
+ bookAppointment.updateConfirmFrame();
}
});
@@ -70,22 +86,31 @@ var bookAppointment = {
if (bindEventHandlers) {
bookAppointment.bindEventHandlers();
}
-
- // Execute other necessary operations on startup.
- $('#select-service').trigger('change');
+
+ // If the manage mode is true, the appointments data should be
+ // loaded by default.
+ if (bookAppointment.manageMode) {
+ bookAppointment.applyAppointmentData(GlobalVariables.appointmentData,
+ GlobalVariables.providerData, GlobalVariables.customerData);
+ } else {
+ $('#select-service').trigger('change'); // Load the available hours.
+ }
},
/**
- * This method binds the necessary event handlers
- * for the book appointments page.
+ * This method binds the necessary event handlers for the book
+ * appointments page.
*/
bindEventHandlers : function() {
/**
* Event : Selected Provider "Changed"
+ *
+ * Whenever the provider changes the available appointment
+ * date - time periods must be updated.
*/
$('#select-provider').change(function() {
bookAppointment.getAvailableHours(Date.today().toString('dd-MM-yyyy'));
- bookAppointment.updateConfirmData();
+ bookAppointment.updateConfirmFrame();
});
/**
@@ -98,21 +123,21 @@ var bookAppointment = {
var currServiceId = $('#select-service').val();
$('#select-provider').empty();
- $.each(GlobalVariables.providers, function(indexProvider, provider) {
+ $.each(GlobalVariables.availableProviders, function(indexProvider, provider) {
$.each(provider['services'], function(indexService, serviceId) {
- // If the current provider is able to provide the selected
- // service, add him to the listbox.
+ // If the current provider is able to provide the selected service,
+ // add him to the listbox.
if (serviceId == currServiceId) {
var optionHtml = '<option value="' + provider['id'] + '">'
- + provider['last_name'] + ' ' + provider['first_name']
- + '</option>';
+ + provider['last_name'] + ' ' + provider['first_name']
+ + '</option>';
$('#select-provider').append(optionHtml);
}
});
});
bookAppointment.getAvailableHours(Date.today().toString('dd-MM-yyyy'));
- bookAppointment.updateConfirmData();
+ bookAppointment.updateConfirmFrame();
});
/**
@@ -123,13 +148,27 @@ var bookAppointment = {
* be perfomed, depending the current wizard step.
*/
$('.button-next').click(function() {
+ // If we are on the 2nd tab then the user should have
+ // an appointment hour selected.
+ if ($(this).attr('data-step_index') === '2') {
+ if ($('.selected-hour').length == 0) {
+ if ($('#select-hour-prompt').length == 0) {
+ $('#available-hours').append('<br><br>'
+ + '<strong id="select-hour-prompt" class="text-error">'
+ + 'Please select an appointment hour before continuing!'
+ + '</strong>');
+ }
+ return;
+ }
+ }
+
// If we are on the 3rd tab then we will need to validate the user's
// input before proceeding to the next step.
- if ($(this).attr('data-step_index') == '3') {
- if (!bookAppointment.validateCustomerDataForm()) {
+ if ($(this).attr('data-step_index') === '3') {
+ if (!bookAppointment.validateCustomerForm()) {
return; // Validation failed, do not continue.
} else {
- bookAppointment.updateConfirmData();
+ bookAppointment.updateConfirmFrame();
}
}
@@ -139,7 +178,7 @@ var bookAppointment = {
$(this).parents().eq(1).hide('fade', function() {
$('.active-step').removeClass('active-step');
$('#step-' + nextTabIndex).addClass('active-step');
- $('#book-appointment-' + nextTabIndex).show('fade');
+ $('#wizard-frame-' + nextTabIndex).show('fade');
});
});
@@ -155,7 +194,7 @@ var bookAppointment = {
$(this).parents().eq(1).hide('fade', function() {
$('.active-step').removeClass('active-step');
$('#step-' + prevTabIndex).addClass('active-step');
- $('#book-appointment-' + prevTabIndex).show('fade');
+ $('#wizard-frame-' + prevTabIndex).show('fade');
});
});
@@ -168,7 +207,7 @@ var bookAppointment = {
$('#available-hours').on('click', '.available-hour', function() {
$('.selected-hour').removeClass('selected-hour');
$(this).addClass('selected-hour');
- bookAppointment.updateConfirmData();
+ bookAppointment.updateConfirmFrame();
});
},
@@ -183,24 +222,31 @@ var bookAppointment = {
// Find the selected service duration (it is going to
// be send within the "postData" object.
var selServiceDuration = 15; // Default value of duration (in minutes).
- $.each(GlobalVariables.services, function(index, service) {
+ $.each(GlobalVariables.availableServices, function(index, service) {
if (service['id'] == $('#select-service').val()) {
selServiceDuration = service['duration'];
}
});
+
+ // If the manage mode is true then the appointment's start
+ // date should return as available too.
+ var appointmentId = (bookAppointment.manageMode)
+ ? GlobalVariables.appointmentData['id'] : undefined;
var postData = {
- 'service_id' : $('#select-service').val(),
- 'provider_id' : $('#select-provider').val(),
- 'selected_date' : selDate,
- 'service_duration' : selServiceDuration
+ 'service_id' : $('#select-service').val(),
+ 'provider_id' : $('#select-provider').val(),
+ 'selected_date' : selDate,
+ 'service_duration' : selServiceDuration,
+ 'manage_mode' : bookAppointment.manageMode,
+ 'appointment_id' : appointmentId
};
// Make ajax post request and get the available hours.
var ajaxurl = GlobalVariables.baseUrl + 'appointments/ajax_get_available_hours';
jQuery.post(ajaxurl, postData, function(postResponse) {
////////////////////////////////////////////////////////////////////////////////
- console.log('\n\n Get Available Hours Post Response :', postResponse, '\n\n');
+ //console.log('\n\n Get Available Hours Post Response :', postResponse, '\n\n');
////////////////////////////////////////////////////////////////////////////////
try {
@@ -209,25 +255,47 @@ var bookAppointment = {
//console.log('\n\n Get Available Hours JSON Response :', jsonResponse, '\n\n');
////////////////////////////////////////////////////////////////////////////////
- // Fill the available time div
- var currColumn = 1;
- $('#available-hours').html('<div style="width:50px; float:left;"></div>');
- $.each(jsonResponse, function(index, availableHour) {
- if ((currColumn * 10) < (index + 1)) {
- currColumn++;
- $('#available-hours').append('<div style="width:50px; float:left;"></div>');
+ if (jsonResponse.length > 0) {
+ // Fill the available time div
+ var currColumn = 1;
+ $('#available-hours').html('<div style="width:50px; float:left;"></div>');
+
+ $.each(jsonResponse, function(index, availableHour) {
+ if ((currColumn * 10) < (index + 1)) {
+ currColumn++;
+ $('#available-hours')
+ .append('<div style="width:50px; float:left;"></div>');
+ }
+
+ $('#available-hours div:eq(' + (currColumn - 1) + ')')
+ .append('<span class="available-hour">' + availableHour
+ + '</span><br/>');
+ });
+
+ if (bookAppointment.manageMode) {
+ // Set the appointment start time as selected.
+ $('.available-hour').removeClass('selected-hour');
+ $('.available-hour').filter(function() {
+ return $(this).text() === Date.parseExact(
+ GlobalVariables.appointmentData['start_datetime'],
+ 'yyyy-MM-dd HH:mm:ss').toString('HH:mm');
+ }).addClass('selected-hour');
+ } else {
+ // Set the first item as selected.
+ $('.available-hour:eq(0)').addClass('selected-hour');
}
-
- $('#available-hours div:eq(' + (currColumn - 1) + ')')
- .append('<span class="available-hour">' + availableHour + '</span><br/>');
- });
-
- // Set the first item as selected.
- $('.available-hour:eq(0)').addClass('selected-hour');
- bookAppointment.updateConfirmData();
+
+ bookAppointment.updateConfirmFrame();
+ } else {
+ $('#available-hours').text('There are no available appointment'
+ + 'hours for the selected date. Please choose another '
+ + 'date.');
+ }
+
} catch(exception) {
- GeneralFunctions.displayMessageBox('Unexpected Error', 'An unexpected error occured '
- + 'during the available hours calculation. Please refresh the page and try again.');
+ GeneralFunctions.displayMessageBox('Unexpected Error', 'An unexpected '
+ + 'error occured during the available hours calculation. Please '
+ + 'refresh the page and try again.');
}
});
},
@@ -238,7 +306,7 @@ var bookAppointment = {
*
* @return {bool} Returns the validation result.
*/
- validateCustomerDataForm : function() {
+ validateCustomerForm : function() {
var validationResult = true;
$('.required').css('border', '');
@@ -257,7 +325,7 @@ var bookAppointment = {
* page with the latest customer settigns and input for the appointment
* booking.
*/
- updateConfirmData : function() {
+ updateConfirmFrame : function() {
/*** SET APPOINTMENT INFO ***/
var selectedDate = $('#select-date').datepicker('getDate');
if (selectedDate !== null) {
@@ -303,6 +371,13 @@ var bookAppointment = {
'id_services' : $('#select-service').val()
};
+ postData['manage_mode'] = bookAppointment.manageMode;
+
+ if (bookAppointment.manageMode) {
+ postData['appointment']['id'] = GlobalVariables.appointmentData['id'];
+ postData['customer']['id'] = GlobalVariables.customerData['id'];
+ }
+
$('input[name="post_data"]').val(JSON.stringify(postData));
},
@@ -316,7 +391,7 @@ var bookAppointment = {
// Find selected service duration.
var selServiceDuration = undefined;
- $.each(GlobalVariables.services, function(index, service) {
+ $.each(GlobalVariables.availableServices, function(index, service) {
if (service.id == $('#select-service').val()) {
selServiceDuration = service.duration;
return; // Stop searching ...
@@ -336,6 +411,47 @@ var bookAppointment = {
}
return endDatetime.toString('yyyy-MM-dd HH:mm:ss');
+ },
+
+ /**
+ * This method applies the appointment's data to the wizard so
+ * that the user can start making changes on an existing record.
+ *
+ * @param {object} appointmentData Selected appointment's data.
+ * @param {object} providerData Selected provider's data.
+ * @param {object} customerData Selected customer's data.
+ * @returns {bool} Returns the operation result.
+ */
+ applyAppointmentData : function(appointmentData, providerData, customerData) {
+ try {
+ // Select Service & Provider
+ $('#select-service').val(appointmentData['id_services']).trigger('change');
+ $('#select-provider').val(appointmentData['id_users_provider']);
+
+ // Set Appointment Date
+ $('#select-date').datepicker('setDate', Date.parseExact(
+ appointmentData['start_datetime'], 'yyyy-MM-dd HH:mm:ss'));
+ bookAppointment.getAvailableHours($('#select-date').val());
+
+ // Apply Customer's Data
+ $('#last-name').val(customerData['last_name']);
+ $('#first-name').val(customerData['first_name']);
+ $('#email').val(customerData['email']);
+ $('#phone-number').val(customerData['phone_number']);
+
+ $('#address').val(customerData['address']);
+ $('#city').val(customerData['city']);
+ $('#zip-code').val(customerData['zip_code']);
+ var appointmentNotes = (appointmentData['notes'] !== null) ? appointmentData['notes'] : '';
+ $('#notes').val(appointmentNotes);
+
+ bookAppointment.updateConfirmFrame();
+
+ return true;
+ } catch(exc) {
+ console.log(exc);
+ return false;
+ }
}
}
@@ -353,7 +469,7 @@ var bookAppointment = {
diff --git a/doc/code-docs/js/functions..html b/doc/code-docs/js/functions..html
index 910dc27e..0be7c93b 100644
--- a/doc/code-docs/js/functions..html
+++ b/doc/code-docs/js/functions..html
@@ -115,7 +115,7 @@ end of the application.
diff --git a/doc/code-docs/js/general_functions.js.html b/doc/code-docs/js/general_functions.js.html
index bae1137b..9aa75b87 100644
--- a/doc/code-docs/js/general_functions.js.html
+++ b/doc/code-docs/js/general_functions.js.html
@@ -109,6 +109,26 @@ GeneralFunctions.centerElementOnPage = function(elementHandle) {
});
});
$(window).resize();
+}
+
+/**
+ * This function retrieves a parameter from a "GET" formed url.
+ *
+ * @link http://www.netlobo.com/url_query_string_javascript.html
+ *
+ * @param {string} url The selected url.
+ * @param {string} name The parameter name.
+ * @returns {String} Returns the parameter value.
+ */
+GeneralFunctions.getUrlParameter = function(url, parameterName) {
+ parameterName = parameterName.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
+ var regexS = "[\\#&]"+parameterName+"=([^]*)";
+ var regex = new RegExp( regexS );
+ var results = regex.exec( url );
+ if( results == null )
+ return "";
+ else
+ return results[1];
}
@@ -125,7 +145,7 @@ GeneralFunctions.centerElementOnPage = function(elementHandle) {
diff --git a/doc/code-docs/js/index.html b/doc/code-docs/js/index.html
index c7094b53..422cba15 100644
--- a/doc/code-docs/js/index.html
+++ b/doc/code-docs/js/index.html
@@ -54,7 +54,7 @@
diff --git a/doc/code-docs/php/class-Appointments.html b/doc/code-docs/php/class-Appointments.html
index 66642888..fce4e140 100644
--- a/doc/code-docs/php/class-Appointments.html
+++ b/doc/code-docs/php/class-Appointments.html
@@ -105,7 +105,7 @@ CI_Controller
- Located atappointments.php
+ Located atappointments.php
@@ -123,20 +123,71 @@ CI_Controller
This page displays the book appointment wizard for the customers.
+
Default callback method of the application.
-
This page displays the book appointment wizard for the customers.
+
Default callback method of the application.
+
+
This method creates the appointment book wizard. If an appointment hash is
+provided then it means that the customer followed the appointment manage link
+that was send with the book success email.
+
Parameters
+
+
$appointment_hash
+
string $appointment_hash The db appointment hash of an existing record.
This method removes an appointment from the company's schedule. In order for
+the appointment to be deleted, the hash string must be provided. The customer
+can only cancel the appointment if the edit time period is not over yet.
+
+
+
+
Parameters
+
+
$appointment_hash
+
string $appointment_hash This is used to distinguish the appointment record.
array $appointment_data Associative array with the appointmet's data. Each key has the
+
array $appointment_data Associative array with the appointment data. Each key has the
same name with the database fields.
@@ -189,8 +189,9 @@ same name with the database fields.
ExpectedException
- ValidationException
- DatabaseException
+ ValidationException Raises when the appointment data are invalid.
+ DatabaseException Raises when the insert or update operation fail to complete
+successfully.
@@ -208,7 +209,7 @@ same name with the database fields.
The appointment data should include the following fields in order to get the
-unique id from the database: start_datetime, end_datetime, id_users_provider,
-id_users_customer, id_services.
+unique id from the database: "start_datetime", "end_datetime",
+"id_users_provider", "id_users_customer", "id_services".
<strong>IMPORTANT!</strong> The record must already exists in the
database, otherwise an exception is raised.
@@ -291,13 +292,14 @@ have the same names as the db fields.
Returns
- integer Returns the id.
+ integer Returns the db id of the record that matches the apppointment data.
ExpectedException
- DatabaseException
+ DatabaseException Raises when this method cannot find any record that matches
+the given data.
@@ -315,18 +317,18 @@ have the same names as the db fields.
Generate a unique hash for the given appointment data.
+
+
+
+
+
+
Generate a unique hash for the given appointment data.
+
+
This method uses the current date-time to generate a unique hash string that
+is later used to identify this appointment. Hash is needed when the email is
+send to the user with an edit link.
+
+
+
+
+
Returns
+
+ string Returns the unique appointment hash.
+
+
+
+
+
diff --git a/doc/code-docs/php/class-Customers_Model.html b/doc/code-docs/php/class-Customers_Model.html
index 3c3e8241..5cb9ee4f 100644
--- a/doc/code-docs/php/class-Customers_Model.html
+++ b/doc/code-docs/php/class-Customers_Model.html
@@ -105,7 +105,7 @@ CI_Model
- Located atcustomers_model.php
+ Located atcustomers_model.php
@@ -258,7 +258,7 @@ necessary fields.
string $setting_name The setting name that is going to be returned.
+
$provider_id
+
integer $provider_id The selected provider id.
+
+
+
Returns
+
+ string Returs the value of the selected user setting.
+
+
+
+
+
diff --git a/doc/code-docs/php/source-class-Appointments.html b/doc/code-docs/php/source-class-Appointments.html
index 108d1313..1f991e1b 100644
--- a/doc/code-docs/php/source-class-Appointments.html
+++ b/doc/code-docs/php/source-class-Appointments.html
@@ -79,222 +79,374 @@
2: 3: classAppointmentsextends CI_Controller {
4: /**
- 5: * This page displays the book appointment wizard
- 6: * for the customers.
- 7: */
- 8: publicfunctionindex() {
- 9: if (strtoupper($_SERVER['REQUEST_METHOD']) != 'POST') {
- 10: // Display the appointment booking page to the customer.
- 11: // Get business name.
- 12: $this->load->model('Settings_Model');
- 13: $view_data['company_name'] = $this->Settings_Model->get_setting('company_name');
- 14:
- 15: // Get the available services and providers.
- 16: $this->load->model('Services_Model');
- 17: $view_data['available_services'] = $this->Services_Model->get_available_services();
- 18:
- 19: $this->load->model('Providers_Model');
- 20: $view_data['available_providers'] = $this->Providers_Model->get_available_providers();
- 21:
- 22: // Load the book appointment view.
- 23: $this->load->view('appointments/book', $view_data);
- 24: } else {
- 25: $post_data = json_decode($_POST['post_data'], true);
- 26:
- 27: // Add customer
- 28: $this->load->model('Customers_Model');
- 29: $customer_id = $this->Customers_Model->add($post_data['customer']);
- 30:
- 31: // Add appointment
- 32: $post_data['appointment']['id_users_customer'] = $customer_id;
- 33: $this->load->model('Appointments_Model');
- 34: $view_data['appointment_id'] = $this->Appointments_Model->add($post_data['appointment']);
- 35:
- 36: // Send an email to the customer with the appointment info.
- 37: $this->load->library('Notifications');
- 38: try {
- 39: $this->notifications->send_book_success($post_data['customer'], $post_data['appointment']);
- 40: $this->notifications->send_new_appointment($post_data['customer'], $post_data['appointment']);
- 41: } catch (NotificationException $not_exc) {
- 42: $view_data['notification_error'] = '<br><br><pre>An unexpected error occured while sending '
- 43: . 'you an email. Please backup the appointment details so that you can restore them '
- 44: . 'later. <br><br>Error:<br>' . $not_exc->getMessage() . '</pre>';
- 45: }
- 46:
- 47: // Load the book appointment view.
- 48: $this->load->view('appointments/book_success', $view_data);
- 49: }
- 50: }
- 51:
- 52: /**
- 53: * [AJAX] Get the available appointment hours for the given date.
- 54: *
- 55: * This method answers to an AJAX request. It calculates the
- 56: * available hours for the given service, provider and date.
- 57: *
- 58: * @param array $_POST['post_data'] An associative array that
- 59: * contains the user selected 'service_id', 'provider_id',
- 60: * 'selected_date' and 'service_duration' in minutes.
- 61: * @return Returns a json object with the available hours.
- 62: */
- 63: publicfunctionajax_get_available_hours() {
- 64: $this->load->model('Providers_Model');
- 65: $this->load->model('Appointments_Model');
- 66: $this->load->model('Settings_Model');
- 67:
- 68: // Get the provider's working plan and reserved appointments.
- 69: $working_plan = json_decode($this->Providers_Model
- 70: ->get_value('working_plan', $_POST['provider_id']), true);
- 71:
- 72: $reserved_appointments = $this->Appointments_Model->get_batch(
- 73: array(
- 74: 'DATE(start_datetime)' => date('Y-m-d', strtotime($_POST['selected_date'])),
- 75: 'id_users_provider' => $_POST['provider_id'],
- 76: 'id_services' => $_POST['service_id']
- 77: ));
- 78:
- 79: // Find the empty spaces on the plan. The first split between
- 80: // the plan is due to a break (if exist). After that every reserved
- 81: // appointment is considered to be a taken space in the plan.
- 82: $sel_date_working_plan = $working_plan[strtolower(date('l',
- 83: strtotime($_POST['selected_date'])))];
- 84: $empty_spaces_with_breaks = array();
- 85:
- 86: if (isset($sel_date_working_plan['breaks'])) {
- 87: foreach($sel_date_working_plan['breaks'] as$index=>$break) {
- 88: // Split the working plan to available time periods that do not
- 89: // contain the breaks in them.
- 90: $last_break_index = $index - 1;
+ 5: * Default callback method of the application.
+ 6: *
+ 7: * This method creates the appointment book wizard. If an appointment hash
+ 8: * is provided then it means that the customer followed the appointment
+ 9: * manage link that was send with the book success email.
+ 10: *
+ 11: * @param string $appointment_hash The db appointment hash of an existing
+ 12: * record.
+ 13: */
+ 14: publicfunctionindex($appointment_hash = '') {
+ 15: if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') {
+ 16: $this->load->model('Settings_Model');
+ 17: $this->load->model('Services_Model');
+ 18: $this->load->model('Providers_Model');
+ 19:
+ 20: $company_name = $this->Settings_Model->get_setting('company_name');
+ 21: $available_services = $this->Services_Model->get_available_services();
+ 22: $available_providers = $this->Providers_Model->get_available_providers();
+ 23:
+ 24: // If an appointment hash is provided then it means that the customer
+ 25: // is trying to edit a registered record.
+ 26: if ($appointment_hash !== ''){
+ 27: // Load the appointments data and set the manage mode of the page.
+ 28: $this->load->model('Appointments_Model');
+ 29: $this->load->model('Customers_Model');
+ 30:
+ 31: $manage_mode = TRUE;
+ 32:
+ 33: $appointment_data = $this->Appointments_Model
+ 34: ->get_batch(array('hash' => $appointment_hash))[0];
+ 35: $provider_data = $this->Providers_Model
+ 36: ->get_row($appointment_data['id_users_provider']);
+ 37: $customer_data = $this->Customers_Model
+ 38: ->get_row($appointment_data['id_users_customer']);
+ 39: } else {
+ 40: // The customer is going to book an appointment so there is no
+ 41: // need for the manage functionality to be initialized.
+ 42: $manage_mode = FALSE;
+ 43: $appointment_data = array();
+ 44: $provider_data = array();
+ 45: $customer_data = array();
+ 46: }
+ 47:
+ 48: // Load the book appointment view.
+ 49: $view_data = array (
+ 50: 'available_services' => $available_services,
+ 51: 'available_providers' => $available_providers,
+ 52: 'company_name' => $company_name,
+ 53: 'manage_mode' => $manage_mode,
+ 54: 'appointment_data' => $appointment_data,
+ 55: 'provider_data' => $provider_data,
+ 56: 'customer_data' => $customer_data
+ 57: );
+ 58: $this->load->view('appointments/book', $view_data);
+ 59: } else {
+ 60: // The page is a post-back. Register the appointment and send
+ 61: // notification emails to the provider and the customer that are
+ 62: // related to the appointment.
+ 63: $post_data = json_decode($_POST['post_data'], true);
+ 64: $appointment_data = $post_data['appointment'];
+ 65: $customer_data = $post_data['customer'];
+ 66:
+ 67: $this->load->model('Customers_Model');
+ 68: $this->load->model('Appointments_Model');
+ 69:
+ 70: $customer_id = $this->Customers_Model->add($customer_data);
+ 71: $appointment_data['id_users_customer'] = $customer_id;
+ 72: $appointment_data['id'] = $this->Appointments_Model->add($appointment_data);
+ 73: $appointment_data['hash'] = $this->Appointments_Model
+ 74: ->get_value('hash', $appointment_data['id']);
+ 75:
+ 76: // Send an email to the customer with the appointment info.
+ 77: $this->load->library('Notifications');
+ 78: try {
+ 79: if (!$post_data['manage_mode']) {
+ 80: $customer_title = 'Your appointment has been successfully booked!';
+ 81: $provider_title = 'A new appointment has been added to your plan.';
+ 82: } else {
+ 83: $customer_title = 'Appointment changes saved successfully!';
+ 84: $provider_title = 'Appointment details have changed.';
+ 85: }
+ 86:
+ 87: $this->notifications->send_book_success(
+ 88: $customer_data, $appointment_data, $customer_title);
+ 89: $this->notifications->send_new_appointment(
+ 90: $customer_data, $appointment_data, $provider_title);
91:
- 92: if (count($empty_spaces_with_breaks) === 0) {
- 93: $start_hour = $sel_date_working_plan['start'];
- 94: $end_hour = $break['start'];
- 95: } else {
- 96: $start_hour = $sel_date_working_plan['breaks'][$last_break_index]['end'];
- 97: $end_hour = $break['start'];
- 98: }
- 99:
-100: $empty_spaces_with_breaks[] = array(
-101: 'start' => $start_hour,
-102: 'end' => $end_hour
-103: );
-104: }
-105:
-106: // Add the space from the last break to the end of the day.
-107: $empty_spaces_with_breaks[] = array(
-108: 'start' => $sel_date_working_plan['breaks'][$index]['end'],
-109: 'end' => $sel_date_working_plan['end']
-110: );
-111: }
-112:
-113: // Break the empty spaces with the reserved appointments.
-114: $empty_spaces_with_appointments = array();
-115: if (count($reserved_appointments) > 0) {
-116: foreach($empty_spaces_with_breaksas$space) {
-117: foreach($reserved_appointmentsas$index=>$appointment) {
-118: $appointment_start = date('H:i', strtotime($appointment['start_datetime']));
-119: $appointment_end = date('H:i', strtotime($appointment['end_datetime']));
-120: $space_start = date('H:i', strtotime($space['start']));
-121: $space_end = date('H:i', strtotime($space['end']));
-122:
-123: if ($space_start < $appointment_start && $space_end > $appointment_end) {
-124: // Current appointment is within the current empty space. So
-125: // we need to break the empty space into two other spaces that
-126: // don't include the appointment.
-127: $empty_spaces_with_appointments[] = array(
-128: 'start' => $space_start,
-129: 'end' => $appointment_start
-130: );
-131: $empty_spaces_with_appointments[] = array(
-132: 'start' => $appointment_end,
-133: 'end' => $space_end
-134: );
-135: } else {
-136: $empty_spaces_with_appointments[] = array(
-137: 'start' => $space_start,
-138: 'end' => $space_end
-139: );
-140: }
-141: }
-142: }
-143: } else {
-144: $empty_spaces_with_appointments = $empty_spaces_with_breaks;
-145: }
-146:
-147: $empty_spaces = $empty_spaces_with_appointments;
-148:
-149: // Calculate the available appointment hours for the given date.
-150: // The empty spaces are broken down to 15 min and if the service
-151: // fit in each quarter then a new available hour is added to the
-152: // $available hours array.
-153: $available_hours = array();
-154:
-155: foreach($empty_spacesas$space) {
-156: $start_hour = new DateTime($_POST['selected_date'] . ' ' . $space['start']);
-157: $end_hour = new DateTime($_POST['selected_date'] . ' ' . $space['end']);
-158: $curr_hour = $start_hour;
-159:
-160: $diff = $curr_hour->diff($end_hour);
-161: while(($diff->h * 60 + $diff->i) > intval($_POST['service_duration'])) {
-162: $available_hours[] = $curr_hour->format('H:i');
-163: $curr_hour->add(new DateInterval("PT15M"));
-164: $diff = $curr_hour->diff($end_hour);
-165: }
-166: }
-167:
-168: // If the selected date is today, remove past hours. It is important
-169: // include the timeout before booking that is set in the backoffice
-170: // the system. Normally we might want the customer to book an appointment
-171: // that is at least half or one hour from now. The setting is stored in
-172: // minutes.
-173: if (date('m/d/Y', strtotime($_POST['selected_date'])) == date('m/d/Y')) {
-174: $book_advance_timeout = $this->Settings_Model->get_setting('book_advance_timeout');
-175:
-176: foreach($available_hoursas$index=>$value) {
-177: $available_hour = strtotime($value);
-178: $current_hour = strtotime('+' . $book_advance_timeout
-179: . ' minutes', strtotime('now'));
+ 92: } catch (NotificationException $not_exc) {
+ 93: $view_data['notification_error'] = '<br><br>'
+ 94: . '<pre>An unexpected error occured while sending you an '
+ 95: . 'email. Please backup the appointment details so that '
+ 96: . 'you can restore them later. <br><br>Error: <br>'
+ 97: . $not_exc->getMessage() . '</pre>';
+ 98: }
+ 99:
+100: // Load the book appointment view.
+101: $view_data['appointment_id'] = $appointment_data['id'];
+102: $this->load->view('appointments/book_success', $view_data);
+103: }
+104: }
+105:
+106: /**
+107: * Cancel an existing appointment.
+108: *
+109: * This method removes an appointment from the company's schedule.
+110: * In order for the appointment to be deleted, the hash string must
+111: * be provided. The customer can only cancel the appointment if the
+112: * edit time period is not over yet.
+113: *
+114: * @param string $appointment_hash This is used to distinguish the
+115: * appointment record.
+116: */
+117: publicfunctioncancel($appointment_hash) {
+118: try {
+119: $this->load->model('Appointments_Model');
+120: $this->load->model('Providers_Model');
+121: $this->load->model('Customers_Model');
+122:
+123: // Check whether the appointment hash exists in the database.
+124: $records = $this->Appointments_Model->get_batch(array('hash' => $appointment_hash));
+125: if (count($records) == 0) {
+126: thrownew Exception('No record matches the provided hash.');
+127: }
+128:
+129: $appointment_data = $records[0];
+130:
+131: // Delete the appointment from the database.
+132: if (!$this->Appointments_Model->delete($appointment_data['id'])) {
+133: thrownew Exception('Appointment could not be deleted from the database.');
+134: }
+135:
+136: // Send notification emails to the customer and provider.
+137: $provider_email = $this->Providers_Model->get_value('email',
+138: $appointment_data['id_users_provider']);
+139: $customer_email = $this->Customers_Model->get_value('email',
+140: $appointment_data['id_users_customer']);
+141:
+142: $this->load->library('Notifications');
+143: $this->notifications->send_cancel_appointment($appointment_data, $provider_email);
+144: $this->notifications->send_cancel_appointment($appointment_data, $customer_email);
+145:
+146: } catch(Exception $exc) {
+147: // Display the error message to the customer.
+148: $view_data['error_message'] = $exc->getMessage();
+149: }
+150:
+151: $this->load->view('appointments/cancel');
+152: }
+153:
+154: /**
+155: * [AJAX] Get the available appointment hours for the given date.
+156: *
+157: * This method answers to an AJAX request. It calculates the
+158: * available hours for the given service, provider and date.
+159: *
+160: * @param array $_POST['post_data'] An associative array that
+161: * contains the user selected 'service_id', 'provider_id',
+162: * 'selected_date' and 'service_duration' in minutes.
+163: * @return Returns a json object with the available hours.
+164: */
+165: publicfunctionajax_get_available_hours() {
+166: $this->load->model('Providers_Model');
+167: $this->load->model('Appointments_Model');
+168: $this->load->model('Settings_Model');
+169:
+170: // Get the provider's working plan and reserved appointments.
+171: $working_plan = json_decode($this->Providers_Model
+172: ->get_setting('working_plan', $_POST['provider_id']), true);
+173:
+174: $where_clause = array(
+175: 'DATE(start_datetime)' => date('Y-m-d', strtotime($_POST['selected_date'])),
+176: 'id_users_provider' => $_POST['provider_id']
+177: );
+178:
+179: $reserved_appointments = $this->Appointments_Model->get_batch($where_clause);
180:
-181: if ($available_hour <= $current_hour) {
-182: unset($available_hours[$index]);
-183: }
-184: }
-185: }
-186:
-187: $available_hours = array_values($available_hours);
-188:
-189: echojson_encode($available_hours);
-190: }
-191:
-192: /**
-193: * Synchronize appointment with its' providers google calendar.
-194: *
-195: * This method syncs the registered appointment with the
-196: * google calendar of the user.
-197: *
-198: * @task This method needs to be changed. Everytime a customer
-199: * books a new appointment the synchronization process must be
-200: * executed.
-201: */
-202: publicfunctiongoogle_sync($appointment_id) {
-203: try {
-204: $this->load->library('Google_Sync');
-205: $this->google_sync->sync_appointment($appointment_id);
-206: $view_data['message'] = 'Your appointment has been successfully added to Google Calendar!';
-207: $view_data['image'] = 'success.png';
-208: } catch (Exception $exc) {
-209: $view_data['message'] = 'An unexpected error occured during the sync '
-210: . 'operation: <br/><pre>' . $exc->getMessage() . '<br/>'
-211: . $exc->getTraceAsString() . '</pre>';
-212: $view_data['image'] = 'error.png';
-213: }
-214:
-215: $this->load->view('appointments/google_sync', $view_data);
-216: }
-217: }
-218:
-219: /* End of file appointments.php */
-220: /* Location: ./application/controllers/appointments.php */
+181: if ($_POST['manage_mode'] === 'true') {
+182: // Current record id shouldn't be included as reserved time,
+183: // whent the manage mode is true.
+184: foreach($reserved_appointmentsas$index=>$appointment) {
+185: if ($appointment['id'] == $_POST['appointment_id']) {
+186: unset($reserved_appointments[$index]);
+187: }
+188: }
+189: }
+190:
+191: // Find the empty spaces on the plan. The first split between
+192: // the plan is due to a break (if exist). After that every reserved
+193: // appointment is considered to be a taken space in the plan.
+194: $sel_date_working_plan = $working_plan[strtolower(date('l',
+195: strtotime($_POST['selected_date'])))];
+196: $empty_spaces_with_breaks = array();
+197:
+198: if (isset($sel_date_working_plan['breaks'])) {
+199: foreach($sel_date_working_plan['breaks'] as$index=>$break) {
+200: // Split the working plan to available time periods that do not
+201: // contain the breaks in them.
+202: $last_break_index = $index - 1;
+203:
+204: if (count($empty_spaces_with_breaks) === 0) {
+205: $start_hour = $sel_date_working_plan['start'];
+206: $end_hour = $break['start'];
+207: } else {
+208: $start_hour = $sel_date_working_plan['breaks'][$last_break_index]['end'];
+209: $end_hour = $break['start'];
+210: }
+211:
+212: $empty_spaces_with_breaks[] = array(
+213: 'start' => $start_hour,
+214: 'end' => $end_hour
+215: );
+216: }
+217:
+218: // Add the space from the last break to the end of the day.
+219: $empty_spaces_with_breaks[] = array(
+220: 'start' => $sel_date_working_plan['breaks'][$index]['end'],
+221: 'end' => $sel_date_working_plan['end']
+222: );
+223: }
+224: // PROBLEM
+225: // Break the empty spaces with the reserved appointments.
+226: $empty_spaces_with_appointments = array();
+227: if (count($reserved_appointments) > 0) {
+228: foreach($empty_spaces_with_breaksas$space) {
+229: foreach($reserved_appointmentsas$index=>$appointment) {
+230: $appointment_start = date('H:i', strtotime($appointment['start_datetime']));
+231: $appointment_end = date('H:i', strtotime($appointment['end_datetime']));
+232: $space_start = date('H:i', strtotime($space['start']));
+233: $space_end = date('H:i', strtotime($space['end']));
+234:
+235: if ($space_start < $appointment_start && $space_end > $appointment_end) {
+236: // Current appointment is within the current empty space. So
+237: // we need to break the empty space into two other spaces that
+238: // don't include the appointment.
+239: $empty_spaces_with_appointments[] = array(
+240: 'start' => $space_start,
+241: 'end' => $appointment_start
+242: );
+243: $empty_spaces_with_appointments[] = array(
+244: 'start' => $appointment_end,
+245: 'end' => $space_end
+246: );
+247: } else {
+248: // Check if there are any other appointments between this
+249: // time space. If not, it is going to be added as it is.
+250: $found = FALSE;
+251: foreach($reserved_appointmentsas$appt) {
+252: $appt_start = date('H:i', strtotime($appt['start_datetime']));
+253: $appt_end = date('H:i', strtotime($appt['end_datetime']));
+254: if ($space_start < $appt_start && $space_end > $appt_end) {
+255: $found = TRUE;
+256: }
+257: }
+258:
+259: // It is also necessary to check that this time period doesn't
+260: // already exist in the "$empty_spaces_with_appointments" array.
+261: $empty_space = array(
+262: 'start' => $space_start,
+263: 'end' => $space_end
+264: );
+265: $already_exist = in_array($empty_space, $empty_spaces_with_appointments);
+266: if ($found === FALSE && $already_exist === FALSE) {
+267: $empty_spaces_with_appointments[] = $empty_space;
+268: }
+269: }
+270: }
+271: }
+272: } else {
+273: $empty_spaces_with_appointments = $empty_spaces_with_breaks;
+274: }
+275:
+276: $empty_spaces = $empty_spaces_with_appointments;
+277:
+278: // Calculate the available appointment hours for the given date.
+279: // The empty spaces are broken down to 15 min and if the service
+280: // fit in each quarter then a new available hour is added to the
+281: // $available hours array.
+282: $available_hours = array();
+283:
+284: foreach($empty_spacesas$space) {
+285: $start_hour = new DateTime($_POST['selected_date'] . ' ' . $space['start']);
+286: $end_hour = new DateTime($_POST['selected_date'] . ' ' . $space['end']);
+287:
+288: $minutes = $start_hour->format('i');
+289:
+290: if ($minutes % 15 != 0) {
+291: // Change the start hour of the current space in order to be
+292: // on of the following: 00, 15, 30, 45.
+293: if ($minutes < 15) {
+294: $start_hour->setTime($start_hour->format('H'), 15);
+295: } elseif ($minutes < 30) {
+296: $start_hour->setTime($start_hour->format('H'), 30);
+297: } elseif ($minutes < 45) {
+298: $start_hour->setTime($start_hour->format('H'), 45);
+299: } else {
+300: $start_hour->setTime($start_hour->format('H') + 1, 00);
+301: }
+302: }
+303:
+304: $curr_hour = $start_hour;
+305:
+306: $diff = $curr_hour->diff($end_hour);
+307: while(($diff->h * 60 + $diff->i) > intval($_POST['service_duration'])) {
+308: $available_hours[] = $curr_hour->format('H:i');
+309: $curr_hour->add(new DateInterval("PT15M"));
+310: $diff = $curr_hour->diff($end_hour);
+311: }
+312: }
+313:
+314: // If the selected date is today, remove past hours. It is important
+315: // include the timeout before booking that is set in the backoffice
+316: // the system. Normally we might want the customer to book an appointment
+317: // that is at least half or one hour from now. The setting is stored in
+318: // minutes.
+319: if (date('m/d/Y', strtotime($_POST['selected_date'])) == date('m/d/Y')) {
+320: if ($_POST['manage_mode'] === 'true') {
+321: $book_advance_timeout = 0;
+322: } else {
+323: $book_advance_timeout = $this->Settings_Model
+324: ->get_setting('book_advance_timeout');
+325: }
+326:
+327: foreach($available_hoursas$index=>$value) {
+328: $available_hour = strtotime($value);
+329: $current_hour = strtotime('+' . $book_advance_timeout
+330: . ' minutes', strtotime('now'));
+331:
+332: if ($available_hour <= $current_hour) {
+333: unset($available_hours[$index]);
+334: }
+335: }
+336: }
+337:
+338: $available_hours = array_values($available_hours);
+339:
+340: echojson_encode($available_hours);
+341: }
+342:
+343: /**
+344: * Synchronize appointment with its' providers google calendar.
+345: *
+346: * This method syncs the registered appointment with the
+347: * google calendar of the user.
+348: *
+349: * @task This method needs to be changed. Everytime a customer
+350: * books a new appointment the synchronization process must be
+351: * executed.
+352: */
+353: publicfunctiongoogle_sync($appointment_id) {
+354: try {
+355: $this->load->library('Google_Sync');
+356: $this->google_sync->sync_appointment($appointment_id);
+357: $view_data['message'] = 'Your appointment has been successfully added'
+358: . 'to Google Calendar!';
+359: $view_data['image'] = 'success.png';
+360: } catch (Exception $exc) {
+361: $view_data['message'] = 'An unexpected error occured during the sync '
+362: . 'operation: <br/><pre>' . $exc->getMessage() . '<br/>'
+363: . $exc->getTraceAsString() . '</pre>';
+364: $view_data['image'] = 'error.png';
+365: }
+366:
+367: $this->load->view('appointments/google_sync', $view_data);
+368: }
+369: }
+370:
+371: /* End of file appointments.php */
+372: /* Location: ./application/controllers/appointments.php */