FAQs


Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Deprecated: html_entity_decode(): Passing null to parameter #1 ($string) of type string is deprecated in /home/na8bb15/addons/wca/wp-content/plugins/ultimate-faqs/includes/template-functions.php on line 354

Google Calendar integration

You must have a different timezone set in your WP settings than in your synced Google Calendar. Make sure they are set to the same timezone and that you’re using a city-based timezone in WP.

No, they can’t. At least not in the latest version of the plugin.

If you need padding, the best way is to include it in your event.

Yes, you can. Use the code snippet below and modify it to your needs.

add_filter( 'woocommerce_appointments_gcal_sync', function( $data, $appointment ) {

    $order = $appointment->get_order();

    $data['attendees'] = [
      // Add staff as attendee
      [       
        'email' => rawurldecode( $appointment->get_staff_members()[0]->user_email),
        'responseStatus' => 'accepted',
        'organizer' => true,
      ],

      // Add client as an attendee
      [                               
        'email' => rawurldecode($order->billing_email),                               
        'responseStatus' => 'accepted',                         
      ]
    ];
		
    return $data;

}, 10, 2);

Yes, you can. Use the code snippet below and modify it to your needs.

add_filter( 'woocommerce_appointments_gcal_sync', function( $data, $appointment ) {

  $order = $appointment->get_order();
  
  $data['location'] = ['Unicorn Road 255, 1000 Neverland, France'];
		
  return $data;

}, 10, 2);

No. You need to be the owner of the calendar for the sync to work.

If someone else created the calendar and shared it with you, that won’t sync.

There can be different reasons for it. Here are the most possible and known reasons.

  1. You must have a different timezone set in your WP settings than in your synced Google Calendar. Make sure they are set to the same timezone and that you’re using a city-based timezone in WP.
  2. You’re using the timezone feature. Make sure that you’re testing the availability in the same timezone that your site is set up in.
  3. You’re using the recurring Google Calendar Events. There is a known bug for this and, until it gets resolved, we suggest that you don’t use recurring events.

Know that the synced gCal event (created automatically from the website when a new appointment is created) is only updated when the appointment status or appointment data are changed.

In case you have some order specific stuff that is synced to the gCal and changes durring the checkout process or later on, you can use a code snippet to re-sync the event.

In the snippet below the re-sync happens when the ORDER status goes from pending-payment to processing or on-hold and that is something that can happen when a customer switches the payment method mid-way the checkout process.

// Trigger re-sync of appointment when order status changes but not the appointment. Use in cases where the user changes the payment method while order is already created.
add_action('woocommerce_order_status_changed', 'resync_status_change', 10, 3);
function resync_status_change($order_id, $old_status, $new_status) {
	if ( $old_status == 'pending' && ( $new_status == 'processing' || $new_status == 'on-hold' )  ) {
    
    $gCal_class = new WC_Appointments_GCal();
		$appointment_ids = WC_Appointment_Data_Store::get_appointment_ids_from_order_id( $order_id );
		foreach ( $appointment_ids as $appointment_id ) {
      $gCal_class->maybe_sync_to_gcal_from_status( $appointment_id );
		}
	}
}

Know that events and appointments created before the initial setup of the gCal integration will not be synced. Only those created after the initial sync will sync.

FYI, if you experience issues with the gCal disconnecting, make sure your app in Google Cloud is set to production mode. More here.

If you set up gCal sync and later change your WP timezone, the already synced Events and Appointments will not be updated in the synced instance. Only new ones created after teh WP change will be changed.

POSSIBLE ISSUE:


NOT AN ISSUE (tested):

  • If the page is under manitenance while updating the theme/plugins/core, events are synced after the update.

Emails

Let’s say this is the template file you want to customize:  woocommerce-appointments\templates\myaccount\reschedule.php

Do not make changes to your theme, but create a child theme. If you don’t know how to do that, check the documentation of your theme.

In your Child theme, create a folder called woocommerce, inside it create a folder called myaccount and copy-paste the reschedule.php file in it. Then modify the file and save changes.

BookingWP’s Appointments is compatible with Kadence WC Email Designer.
These should all be available in appointment (NOT order) emails:

{appointment_number}
{appointment_start}
{appointment_end}
{appointment_title}
{order_date}
{order_number}
{customer_first_name}
{customer_last_name}
{customer_full_name}
{customer_username}
{customer_email}

Availability

If this happens, you probably have many rules set. Try increasing your server’s “max_input_vars” value.

For this purpose, use the Staff functionality.

How it works: Link the same staff to Product A, product B, and Product C. When Product A is booked for 8-10am on 15.12.2030, Product B and Product C will not be available at that same time (8-10am on 15.12.2030).

Staff

I’ll try to explain via an example:

Product A has staff Bob assigned to it. The product duration is set to 1 day and padding to 0.

Product B has staff Bob (same staff) assigned to it. The product duration is set to 1 day and padding to 1.

If you book Product A on 25.10., Product B is available on 26.10. Because padding only applies to Product B.

If you book Product B on 15.10., Product A is NOT available on the 16.10. because the padding is applied to Product B.

If you need padding to be the same for both Products, then use the same padding for both. Otherwise, this allows a more detailed configuration of different padding for products that share the same staff.

add_filter( 'appointment_form_fields', function( $fields ){
  /*
   * $fields['wc_appointments_field_staff']
   * $fields['wc_appointments_field_start_date']
   * $fields['wc_appointments_field_addons_duration']
   * $fields['wc_appointments_field_addons_cost']
   */
  $start = $fields['wc_appointments_field_start_date'];
  unset($fields['wc_appointments_field_start_date']);
  array_unshift($fields, $start);
  return $fields;
} );
add_filter( 'woocommerce_appointments_get_posted_data', 'dont_randmize_staff_selection', 10, 3 );
function dont_randmize_staff_selection( $data, $product, $posted ) {
	if ( $product->has_staff() && $product->is_staff_assignment_type( 'automatic' ) ) {
			// Assign an available staff automatically
			$available_appointments = wc_appointments_get_total_available_appointments_for_range( $product, $data['_start_date'], $data['_end_date'], 0, $data['_qty'] );

			if ( is_array( $available_appointments ) ) {
				$shuffleKeys = array_keys( $available_appointments );
				$current_staff = current( $shuffleKeys );
				
				// Check if there are priority staff (IDs) available
				$priority = array();
				foreach( $shuffleKeys as $key ) {
					if ($key == 577) { $priority[] = 577; }
				}
				// If there are, randomly select one of the priority
				if (!empty($priority)) {
					shuffle($priority);
					$current_staff = current( $priority );
				} else {
					// If no staff from priority is available, choose a random one.
					shuffle( $shuffleKeys );
					$current_staff = current( $shuffleKeys );
				}

				$staff = get_user_by( 'id', $current_staff );
				$data['_staff_id'] = $current_staff;
				$data['staff']     = $staff->display_name;
			}
	}

	return $data;
}

Third-party plugin integration

This is possible if you combine our Appointments with extensions like this one. Another option is to sell customers credits they can spend later with something like this or this.

Random

This is possible if you combine our Appointments with extensions like this one. Another option is to sell customers credits they can spend later with something like this or this.

You can use this SQL sentence to delete all appointments created before November 1st 2023:

DELETE p, pm
  FROM wp_posts p
 INNER 
  JOIN wp_postmeta pm
    ON pm.post_id = p.ID
 WHERE p.post_type LIKE 'wc_appointment'
   AND p.post_date < '2023-11-01 00:00:00';

Modify the date to your needs.

What will happen to orders containing deleted appointments?

Orders will be available but instead of the appointment ID and other data they will contain only the product name.

Let’s say this is the template file you want to customize:  woocommerce-appointments\templates\myaccount\reschedule.php

Do not make changes to your theme, but create a child theme. If you don’t know how to do that, check the documentation of your theme.

In your Child theme, create a folder called woocommerce, inside it create a folder called myaccount and copy-paste the reschedule.php file in it. Then modify the file and save changes.

Product Input Fields for WooCommerce (reported here)

Bricks Builder – everything works fine except for the “AJAX add to cart” in the edit-product page. It needs to be disabled.

Useful code snippets

// [1214, 3308, 3309] are products ids for my specific produts in three language versions.
// Make sure to adapt ids and so on according to your own needs!
add_filter( 'woocommerce_appointments_time_slots', 'remove_all_scheduled_time_slots', 10, 9 );
function remove_all_scheduled_time_slots( $available_slots, $slots, $intervals, $time_to_check, $staff_id, $from, $to, $tzstring, $appointable_product ) {
	if ( $available_slots and in_array($appointable_product->id, [1214, 3308, 3309]) ) {
        foreach ( $available_slots as $available_slot => $available_quantity ) {
            if ( $available_quantity['scheduled'] ) {
                unset( $available_slots[ $available_slot ] );
            }
        }
    }
 
    return $available_slots;
}
add_filter( 'wc_appointment_form_params', 'bwp_calendar_filter_picker_options' );
function bwp_calendar_filter_picker_options( $appointment_form_params ) {
    $appointment_form_params['showOtherMonths'] = false;
    return $appointment_form_params;
}

Know that the synced gCal event (created automatically from the website when a new appointment is created) is only updated when the appointment status or appointment data are changed.

In case you have some order specific stuff that is synced to the gCal and changes durring the checkout process or later on, you can use a code snippet to re-sync the event.

In the snippet below the re-sync happens when the ORDER status goes from pending-payment to processing or on-hold and that is something that can happen when a customer switches the payment method mid-way the checkout process.

// Trigger re-sync of appointment when order status changes but not the appointment. Use in cases where the user changes the payment method while order is already created.
add_action('woocommerce_order_status_changed', 'resync_status_change', 10, 3);
function resync_status_change($order_id, $old_status, $new_status) {
	if ( $old_status == 'pending' && ( $new_status == 'processing' || $new_status == 'on-hold' )  ) {
    
    $gCal_class = new WC_Appointments_GCal();
		$appointment_ids = WC_Appointment_Data_Store::get_appointment_ids_from_order_id( $order_id );
		foreach ( $appointment_ids as $appointment_id ) {
      $gCal_class->maybe_sync_to_gcal_from_status( $appointment_id );
		}
	}
}

If you want to have 10 slots for one product that can be booked on different days totaling at 10, you can use the snippet below. It will reduce the product inventory by the number of booked appointments. But it will not add the number when canceled – although it can be done with another hook.

// Decrease the overall product inventory for boked appointable product when booked.
// Triggered when the order is created (when appointment status goes from in-cart to anything that means order created).
// 
// Keep in mind that it will not work for appointments in cart although that could also be ahived by utilizing another hook for reducing the amount and one more hook to sdding (if order is not finished)..
// It will also not add the amout if the appointment is cancelled but it could be achieved by utilizing another hook.
add_action( 'woocommerce_appointment_in-cart_to_paid', 'decrease_appointable_product_inventory' );
add_action( 'woocommerce_appointment_in-cart_to_unpaid', 'decrease_appointable_product_inventory' );
add_action( 'woocommerce_appointment_in-cart_to_pending-confirmation', 'decrease_appointable_product_inventory' );
function decrease_appointable_product_inventory( $appointment_id ){
  $appointment = get_wc_appointment( $appointment_id );
  $product_id = $appointment->get_product_id();
  $product = wc_get_product( $product_id );
  $quantity = $appointment->get_qty();
  $product_iventory = get_post_meta( $product_id, '_wc_appointment_qty', true );
  $reduced_num = intval($product_iventory) - intval($quantity);
  
  update_post_meta( $product_id, 'test vale', $quantity . ' ' . $product_iventory );
}

You can use this SQL sentence to delete all appointments created before November 1st 2023:

DELETE p, pm
  FROM wp_posts p
 INNER 
  JOIN wp_postmeta pm
    ON pm.post_id = p.ID
 WHERE p.post_type LIKE 'wc_appointment'
   AND p.post_date < '2023-11-01 00:00:00';

Modify the date to your needs.

What will happen to orders containing deleted appointments?

Orders will be available but instead of the appointment ID and other data they will contain only the product name.

// Get all appointment IDs from an order
$appointments_ids = WC_Appointment_Data_Store::get_appointment_ids_from_order_id( $order->get_id() );
	
// if there are appointments in order, proceed
if ($appointments_ids) {
  // loop through appointments
  foreach($appointments_ids as $appointment_id) {
    // Get appointment
    $appointment = get_wc_appointment($appointment_id);

    // get appointment ID
    $appointment_id;
			
    // get appointment start datetime - Example: 06/09/2023, 08:30
    $start_date = $appointment->get_start_date();

    // get appointment start date only (no time)  
    $start_date_only = $appointment->get_start_date( wc_date_format(), '' );

    // get start time only (no date)
    $start_time_only = $appointment->get_start_date( '', wc_time_format() );
		
    // get appointment end datetime - Example: 06/09/2023, 10:30
    $end_date = $appointment->get_end_date();
		
    // get appointment duration
    $duration = $appointment->get_duration();
	
    // get appointment addons
    $addons = $appointment->get_addons();
		
    // get appointment's product name
    $product_name = $appointment->get_product_name();
		
    // get appointment quantity
    $quantity = $appointment->get_qty();
    
    // get Customer's full name
    $customer = $appointment->get_customer();
    $customer_name = $customer->full_name;
		
    // get staff first & last names divided by a column
    $staff_ids = $appointment->get_staff_ids();
    $staff_html = '';
    if ($staff_ids) {
      foreach ($staff_ids as $staff_id) {
        $user_meta = get_user_meta( intval($staff_id) );
        $staff .= $user_meta['first_name'][0] . ' ' . $user_meta['last_name'][0] . ', ';
      }
      $staff_html = rtrim($staff, ', ');
    }		
  }
}
add_filter( 'woocommerce_appointments_get_posted_data', 'dont_randmize_staff_selection', 10, 3 );
function dont_randmize_staff_selection( $data, $product, $posted ) {
	if ( $product->has_staff() && $product->is_staff_assignment_type( 'automatic' ) ) {
			// Assign an available staff automatically
			$available_appointments = wc_appointments_get_total_available_appointments_for_range( $product, $data['_start_date'], $data['_end_date'], 0, $data['_qty'] );

			if ( is_array( $available_appointments ) ) {
				$shuffleKeys = array_keys( $available_appointments );
				$current_staff = current( $shuffleKeys );
				
				// Check if there are priority staff (IDs) available
				$priority = array();
				foreach( $shuffleKeys as $key ) {
					if ($key == 577) { $priority[] = 577; }
				}
				// If there are, randomly select one of the priority
				if (!empty($priority)) {
					shuffle($priority);
					$current_staff = current( $priority );
				} else {
					// If no staff from priority is available, choose a random one.
					shuffle( $shuffleKeys );
					$current_staff = current( $shuffleKeys );
				}

				$staff = get_user_by( 'id', $current_staff );
				$data['_staff_id'] = $current_staff;
				$data['staff']     = $staff->display_name;
			}
	}

	return $data;
}

Hooks (filters & actions)

// Show extra price of addons in the order view
add_filter('woocommerce_addons_add_order_price_to_value', function(){
  return true;
});
do_action( 'woocommerce_admin_new_appointment_notification', $appointment_id );
do_action( 'wc-appointment-confirmed', $appointment_id );
do_action( 'wc-appointment-reminder', $appointment_id );
do_action( 'wc-appointment-complete', $appointment_id );
do_action( 'wc-appointment-follow-up', $appointment_id );
do_action( 'wc-appointment-remove-inactive-cart', $appointment_id );
// Appointment is created
do_action( 'woocommerce_new_appointment', $appointment_id );

// Appointment is rescheduled
do_action( 'woocommerce_appointments_rescheduled_appointment', $appointment_id, $prev_start_date, $prev_end_date );

// Appointment is cancelled
do_action( 'woocommerce_appointment_cancelled', $new_appointment_id, $new_appointment );

When creating a new appointment in admin there is no option to check the checkbox to send SMS notifications like on the frontend (Checkout page). But you can use the code snippet below to automatically enable it for all new orders created in the backend when creating a new Appointment.

Know that this will not work if you’re attaching an appointment to an existing order.

// Automatically enable Twilio SMS when creating an order in admin
add_action('woocommerce_new_appointment_order', function( $order_id ){
  add_post_meta( $order_id, '_wc_twilio_sms_optin', 1 );
});

Addons

// Show extra price of addons in the order view
add_filter('woocommerce_addons_add_order_price_to_value', function(){
  return true;
});
function add_js_code() {
  echo "jQuery(document).ready(function(){
  jQuery('.wc-pao-addon-select')[0].selectedIndex = 1;
  
  // select first image dropdwon
  jQuery('.wc-pao-addon-image-swatch-select')[0].selectedIndex = 1;
  jQuery('.wc-pao-addon-container a:nth-child(1)').addClass('selected');
  
  });";
}
add_action( 'wp_footer', 'add_js_code' );

// More variations here: https://bookingwp.com/forums/topic/set-default-add-on/

// Autoselect a specific radio button addon
// jQuery("[value='rondtukken']").prop("checked", true).change();

You can export your product addons and import them to other products/sites.

TO EXPORT: In the edit-product page, go in the Add-ons tab, click “Export” and copy the contents.

TO IMPORT: Go to the same location and click the “Import” button. Paste the contents and save product changes.

DRAFT

NOTE: use this to help in writing it: https://bookingwp.com/forums/topic/problem-with-booking-times-and-availability-times/

Export/Import/Migrate

Use this plugin and add the code snippet to your site to include the appointment data.

// Export Appointments data with "Advanced Order Export For WooCommerce"
// Plugin link: https://wordpress.org/plugins/woo-order-export-lite/
class WOE_Appointments{
    function __construct() {
        add_filter('woe_get_order_product_fields',array($this,'add_product_fields') );
        add_filter('woe_get_order_product_item',array($this,'fetch_appointment') );
        
        add_filter('woe_get_order_product_value_appointment_status', array($this,'get_appointment_field_status'), 10, 4 );
        add_filter('woe_get_order_product_value_appointment_start_date', array($this,'get_appointment_field_start_date'), 10, 4 );
        add_filter('woe_get_order_product_value_appointment_start_time', array($this,'get_appointment_field_start_time'), 10, 4 );
        add_filter('woe_get_order_product_value_appointment_end_date', array($this,'get_appointment_field_end_date'), 10, 4 );
        add_filter('woe_get_order_product_value_appointment_end_time', array($this,'get_appointment_field_end_time'), 10, 4 );
        add_filter('woe_get_order_product_value_appointment_staff', array($this,'get_appointment_field_staff'), 10, 4 );
		add_filter('woe_get_order_product_value_appointment_addons', array($this,'get_appointment_field_addons'), 10, 4 );
    }
    
    function add_product_fields($fields) {
        $fields['appointment_status'] = array('label'=>'Appointment Status','colname'=>'Appointment Status','checked'=>1,'segment'=>'cart');
        $fields['appointment_start_date'] = array('label'=>'Appointment Start Date','colname'=>'Appointment Start Date','checked'=>1,'segment'=>'cart');
        $fields['appointment_start_time'] = array('label'=>'Appointment Start Time','colname'=>'Appointment Start Time','checked'=>1,'segment'=>'cart');
        $fields['appointment_end_date'] = array('label'=>'Appointment End Date','colname'=>'Appointment End Date','checked'=>1,'segment'=>'cart');
        $fields['appointment_end_time'] = array('label'=>'Appointment End Time','colname'=>'Appointment End Time','checked'=>1,'segment'=>'cart');
        $fields['appointment_staff'] = array('label'=>'Appointment Staff','colname'=>'Appointment Staff','checked'=>1,'segment'=>'cart');
		    $fields['appointment_addons'] = array('label'=>'Appointment Addons','colname'=>'Appointment Addons','checked'=>1,'segment'=>'cart');
        return $fields;
    }
    
    // appointment for item 
    function fetch_appointment($item) {
        global $wpdb;
        $this->appointment = false;
        $appointment_id = $wpdb->get_var( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key= '_appointment_order_item_id' AND meta_value=" . intval( $item->get_id() ) );
        if( $appointment_id ) {
            $this->appointment =  new WC_Appointment($appointment_id);
        }
        return $item;
    }
    
    function get_appointment_field_status($value,$order, $item, $product) {
        return $this->appointment ? wc_appointments_get_status_label( $this->appointment->get_status() ) : $value;
    }        
    function get_appointment_field_start_date($value,$order, $item, $product) {
        return $this->appointment ? date_i18n( wc_date_format(), $this->appointment->start) : $value;
    }        
    function get_appointment_field_start_time($value,$order, $item, $product) {
        return $this->appointment ? date_i18n( wc_time_format(), $this->appointment->start) : $value;
    }        
    function get_appointment_field_end_date($value,$order, $item, $product) {
        return $this->appointment ? date_i18n( wc_date_format(), $this->appointment->end) : $value;
    }        
    function get_appointment_field_end_time($value,$order, $item, $product) {
        return $this->appointment ? date_i18n( wc_time_format(), $this->appointment->end) : $value;
    }        
    function get_appointment_field_staff($value,$order, $item, $product) {
        if (!$this->appointment) 
            return $value;
        /*$staff = $this->appointment->get_staff_members( true, false );
        return $staff ? $staff : $value;*/
		
		$staff_ids = $this->appointment->get_staff_ids();
		if ($staff_ids) {
			//return $staff_ids;
			foreach ($staff_ids as $staff_id) {
				//return $staff_id;
				$user_meta = get_user_meta( intval($staff_id) );
				//return $user_meta;
				$staff .= $user_meta['first_name'][0] . ' ' . $user_meta['last_name'][0] . ', ';
			}
			return rtrim($staff, ', ');
		}
        return '';
    }
	function get_appointment_field_addons($value,$order, $item, $product) {
        if (!$this->appointment) 
            return $value;
        
		$addons_html = wp_strip_all_tags($this->appointment->get_addons());
		if (!empty($addons_html))
	        return preg_replace('#\s+#',', ',trim($addons_html));
    }
}
new WOE_Appointments();

Explanation

I’ll try to explain via an example:

Product A has staff Bob assigned to it. The product duration is set to 1 day and padding to 0.

Product B has staff Bob (same staff) assigned to it. The product duration is set to 1 day and padding to 1.

If you book Product A on 25.10., Product B is available on 26.10. Because padding only applies to Product B.

If you book Product B on 15.10., Product A is NOT available on the 16.10. because the padding is applied to Product B.

If you need padding to be the same for both Products, then use the same padding for both. Otherwise, this allows a more detailed configuration of different padding for products that share the same staff.

Twilio SMS integration

When creating a new appointment in admin there is no option to check the checkbox to send SMS notifications like on the frontend (Checkout page). But you can use the code snippet below to automatically enable it for all new orders created in the backend when creating a new Appointment.

Know that this will not work if you’re attaching an appointment to an existing order.

// Automatically enable Twilio SMS when creating an order in admin
add_action('woocommerce_new_appointment_order', function( $order_id ){
  add_post_meta( $order_id, '_wc_twilio_sms_optin', 1 );
});

Troubleshooting

If this happens, you probably have many rules set. Try increasing your server’s “max_input_vars” value.