first of all at the end of using a new WP_Query it’s good pratice to always use wp_reset_postdata(); to be sure nothing had compromise your database.
$already_reserved = new WP_Query(array(
'post_type' => 'appointments',
'fields' => 'ids',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'datetime',
'value' => $date_time, // timestamps
'type' => 'numeric',
'compare' => '='
)
),
'tax_query' => array(
array(
'taxonomy' => 'services',
'field' => 'id',
'terms' => $service_id,
)
)
wp_reset_postdata();
));
and to answer your question, why don’t use another if when the button is pressed, right before the appointment is set on the database. like so
if ( !empty($already_reserved->posts) ) {
$response="Sorry, this date is full.";
} else {
// create new appointment post
// ...
// the user has everything and click the button
if ( !empty($already_reserved->posts) ) {
$response="Sorry, this date is no longer available.";
} else {
// the appointment is set on the database
}
}
this is supposing that first there is a validation of the date and only after that the user / users press the button to confirm.
if the button has a specific function try to edit that function to make this validation before set the appointment on the database.