# booktics/1.0.11/core/appointment/services/appointment-service.php

Booktics – Appointment Booking Calendar for Service Businesses, version 1.0.11. 203 lines.

- Page: https://pluginprobe.com/plugins/booktics/1.0.11/code/core/appointment/services/appointment-service.php
- Raw: https://pluginprobe.com/plugins/booktics/1.0.11/raw/core/appointment/services/appointment-service.php
- Modified: 2025-11-09T05:37:04+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/booktics/1.0.11/code/core/appointment/services/appointment-service.php#L10-L20`.

```php
<?php
namespace Booktics\Appointment\Services;

use Booktics\Abstracts\Booktics_Database;
use WP_Error;
use Booktics\Utils\Singleton;
use Booktics\Models\Appointment_Model;
use Booktics\Models\Guest_Model;
use Booktics\Models\Order_Model;

class Appointment_Service {

    use Singleton;

    /**
     * Create a new appointment.
     * @param array $data
     * @return mixed (Appointment object or WP_Error)
     */
    public static function create( array $data ) {
        // Sanitize and prepare data as needed
        $appointment = Appointment_Model::create( $data );
        if ( $appointment && ! is_wp_error( $appointment ) ) {
            // Ensure a security token is always present for new appointments
            $appointment->add_security_token_if_not_exists();
        }
        return $appointment;
    }

    /**
     * Update an existing appointment.
     * @param int $id
     * @param array $data
     * @return mixed (Updated appointment object or WP_Error)
     */
    public static function update( int $id, array $data ) {
        $appointment = Appointment_Model::find( $id );
        if ( ! $appointment ) {
            return new WP_Error( 'not_found', __( 'Appointment not found', 'booktics' ) );
        }

        // Store original status to check if it's being changed
        $original_status = $appointment->status;
        $new_status = isset( $data['status'] ) ? $data['status'] : null;

        // Optionally handle customer update
        if ( ! empty( $data['customer'] ) ) {
            $db = new Booktics_Database();
            $customer = ( new Guest_Model( $db ) )->find( array( 'id' => $appointment->customer_id ) );
            if ( $customer ) {
                $updated_customer = ( new Guest_Model( $db ) )
                ->update( $appointment->customer_id, $data['customer'] );
                unset( $data['customer'] );
                $appointment->customer = $updated_customer;
            }
        }

        $updated_appointment = $appointment->update( $data );
        if ( is_wp_error( $updated_appointment ) ) {
            return $updated_appointment;
        }

        // Update associated order status if appointment status changed
        if ( $new_status && $new_status !== $original_status ) {
            self::sync_order_status( $appointment, $new_status );
        }

        return $updated_appointment;
    }

    /**
     * Update associated order status when appointment status changes
     *
     * @param object $appointment The appointment object
     * @param string $new_status The new status to set for the order
     * @return void
     */
    private static function sync_order_status( $appointment, string $new_status ) {
        // Get order_id using fillable property accessor with post meta fallback
        $order_id = $appointment->order_id ?: get_post_meta( $appointment->id, 'order_id', true );

        if ( empty( $order_id ) ) {
            return;
        }

        $db = new Booktics_Database();
        $order_model = new Order_Model( $db );
        $order = $order_model->find( array( 'id' => $order_id ) );

        if ( ! $order || is_wp_error( $order ) ) {
            return;
        }

        $order_model->update( $order_id, array( 'status' => $new_status ) );
    }

    /**
     * Delete an appointment by ID.
     * @param int $id
     * @return mixed (Deleted appointment object or WP_Error)
     */
    public static function delete( int $id ) {
        $appointment = Appointment_Model::find( $id );
        if ( ! $appointment ) {
            return new WP_Error( 'not_found', __( 'Appointment not found', 'booktics' ) );
        }
        $deleted = $appointment->delete();
        if ( is_wp_error( $deleted ) ) {
            return $deleted;
        }
        return $appointment;
    }

    /**
     * Delete multiple appointments by IDs.
     * @param array $ids
     * @return array|WP_Error
     */
    public static function delete_many( array $ids ) {
        if ( ! is_array( $ids ) ) {
            return new WP_Error( 'invalid_ids', __( 'Ids should be an array of items ids', 'booktics' ) );
        }
        $count = 0;
        $total = count( $ids );
        $deleted = array();
        foreach ( $ids as $id ) {
            $appointment = Appointment_Model::find( $id );
            if ( $appointment ) {
                $appointment->delete();
                $deleted[] = $appointment;
                ++$count;
            }
        }
        if ( $count === 0 ) {
            return new WP_Error( 'none_deleted', __( 'No item deleted.', 'booktics' ) );
        }
        $message = sprintf( 'Deleted %d items of %d', $count, $total );
        return array(
			'data' => $deleted,
			'message' => $message,
		);
    }

    /**
     * Reschedule an appointment.
     * @param int $id
     * @param array $data
     * @return mixed (Updated appointment object or WP_Error)
     */
    public static function reschedule( int $id, array $data ) {
        $appointment = Appointment_Model::find( $id );
        if ( ! $appointment ) {
            return new WP_Error( 'not_found', __( 'Appointment not found', 'booktics' ) );
        }
        $reschedule_data = array(
            'start_time' => sanitize_text_field( $data['start_time'] ),
			'end_time'   => sanitize_text_field( $data['end_time'] ),
			'date'       => sanitize_text_field( $data['date'] ),
        );
        $customer_reschedule_status = booktics_get_option( 'customer_reschedule_status', array( 'enable' => false ) );
        if ( $customer_reschedule_status['enable'] ) {
            $reschedule_data['status'] = $customer_reschedule_status['status'];
        }
        $updated = $appointment->update( $reschedule_data );
        if ( is_wp_error( $updated ) ) {
            return new WP_Error( 'reschedule_error', __( 'Appointment reschedule error.', 'booktics' ) );
        }
        // Optionally fire hooks here
        return $appointment;
    }

    /**
     * Cancel an appointment.
     * @param int $id
     * @param array $data Additional data like cancel_note
     * @return mixed (Updated appointment object or WP_Error)
     */
    public static function cancel( int $id, array $data = [] ) {
        $appointment = Appointment_Model::find( $id );
        if ( ! $appointment ) {
            return new WP_Error( 'not_found', __( 'Appointment not found', 'booktics' ) );
        }

        $update_data = [ 'status' => 'canceled' ];

        if ( ! empty( $data['cancel_note'] ) ) {
            $update_data['cancel_note'] = sanitize_text_field( $data['cancel_note'] );
        }

        $updated = $appointment->update( $update_data );

        if ( is_wp_error( $updated ) ) {
            return new WP_Error( 'cancel_error', __( 'Appointment cancellation error.', 'booktics' ) );
        }

        // Update associated order status to canceled
        self::sync_order_status( $appointment, 'canceled' );

        // Optionally fire hooks here
        return $appointment;
    }
}

```
