[Back]
<?php
/*
 * Copyright (c) 2023 LatePoint LLC. All rights reserved.
 */

class OsOrdersHelper {

	/**
	 * @param $order_item_id
	 * @param $service_id
	 *
	 * @return OsBookingModel[]
	 */
	public static function get_bookings_for_order_item( $order_item_id, $service_id = false, array $only_statuses = [] ): array {
		$bookings = new OsBookingModel();
		if ( $service_id ) {
			$bookings = $bookings->where( [ 'service_id' => $service_id ] );
		}
		if ( ! empty( $only_statuses ) ) {
			$bookings = $bookings->where( [ 'status' => $only_statuses ] );
		}
		$bookings = $bookings->where( [ 'order_item_id' => $order_item_id ] )->order_by( 'start_datetime_utc asc' )->get_results_as_models();

		return $bookings;
	}

	public static function quick_order_btn_html( $order_id = false, $params = array() ) {
		$html = '';
		if ( $order_id ) {
			$params['id'] = $order_id;
		}
		$route = OsRouterHelper::build_route_name( 'orders', 'quick_edit' );

		$params_str = http_build_query( $params );
		$html       = 'data-os-params="' . esc_attr( $params_str ) . '" 
    data-os-action="' . esc_attr( $route ) . '" 
    data-os-output-target="side-panel"
    data-os-after-call="latepoint_init_quick_order_form"';

		return $html;
	}

	public static function get_order_statuses_list() {
		$statuses = [
			LATEPOINT_ORDER_STATUS_OPEN      => __( 'Open', 'latepoint' ),
			LATEPOINT_ORDER_STATUS_CANCELLED => __( 'Cancelled', 'latepoint' ),
			LATEPOINT_ORDER_STATUS_COMPLETED => __( 'Completed', 'latepoint' )
		];

		/**
		 * Get list of statuses for orders
		 *
		 * @param {array} $statuses array of order status codes
		 * @returns {array} The filtered array of status codes
		 *
		 * @since 5.0.0
		 * @hook latepoint_order_statuses
		 *
		 */
		return apply_filters( 'latepoint_order_statuses', $statuses );
	}

	public static function bundle_services_and_booked_count_for_order_item_id( int $order_item_id ): array {
		$order_item      = new OsOrderItemModel( $order_item_id );
		$bundle_services = [];
		if ( $order_item->is_bundle() ) {
			$bundle          = $order_item->build_original_object_from_item_data();
			$bundle_services = $bundle->get_services( $order_item_id );
		}

		return $bundle_services;
	}


	public static function get_items_for_order_id( $order_id ) {
		$order_items = new OsOrderItemModel();

		return $order_items->where( [ 'order_id' => $order_id ] )->get_results_as_models();
	}

	public static function create_order_item_from_cart_item( OsCartItemModel $cart_item ): OsOrderItemModel {
		$order_item                  = new OsOrderItemModel();
		$order_item->variant         = $cart_item->variant;
		$order_item->item_data       = $cart_item->item_data;
		$order_item->total           = $cart_item->get_total();
		$order_item->subtotal        = $cart_item->get_subtotal();
		$order_item->coupon_code     = $cart_item->get_coupon_code();
		$order_item->coupon_discount = $cart_item->get_coupon_discount();
		$order_item->tax_total       = $cart_item->get_tax_total();

		return $order_item;
	}


	public static function get_fulfillment_statuses_list(): array {
		$statuses = [
			LATEPOINT_ORDER_FULFILLMENT_STATUS_NOT_FULFILLED       => __( 'Not Fulfilled', 'latepoint' ),
			LATEPOINT_ORDER_FULFILLMENT_STATUS_FULFILLED           => __( 'Fulfilled', 'latepoint' ),
			LATEPOINT_ORDER_FULFILLMENT_STATUS_PARTIALLY_FULFILLED => __( 'Partially Fulfilled', 'latepoint' )
		];

		/**
		 * Get list of fulfillment statuses for orders
		 *
		 * @param {array} $statuses array of order fulfillment status codes
		 * @returns {array} The filtered array of fulfillment status codes
		 *
		 * @since 5.0.0
		 * @hook latepoint_order_fulfillment_statuses
		 *
		 */
		return apply_filters( 'latepoint_order_fulfillment_statuses', $statuses );
	}

	public static function get_order_payment_statuses_list(): array {
		$statuses = [
			LATEPOINT_ORDER_PAYMENT_STATUS_NOT_PAID       => __( 'Not Paid', 'latepoint' ),
			LATEPOINT_ORDER_PAYMENT_STATUS_PARTIALLY_PAID => __( 'Partially Paid', 'latepoint' ),
			LATEPOINT_ORDER_PAYMENT_STATUS_FULLY_PAID     => __( 'Fully Paid', 'latepoint' ),
			LATEPOINT_ORDER_PAYMENT_STATUS_PROCESSING     => __( 'Processing', 'latepoint' ),
		];

		/**
		 * Get list of fulfillment statuses for orders
		 *
		 * @param {array} $statuses array of order payment status codes
		 * @returns {array} The filtered array of payment status codes
		 *
		 * @since 5.0.0
		 * @hook latepoint_order_payment_statuses
		 *
		 */
		return apply_filters( 'latepoint_order_payment_statuses', $statuses );
	}

	public static function get_default_order_status(): string {
		$status = LATEPOINT_ORDER_STATUS_OPEN;

		/**
		 * Get default order status
		 *
		 * @param {string} $status a default order status
		 * @returns {string} filtered status
		 *
		 * @since 5.0.0
		 * @hook latepoint_get_default_order_status
		 *
		 */
		return apply_filters( 'latepoint_get_default_order_status', $status );
	}


	public static function get_nice_order_payment_status_name( $status ) {
		$statuses_list = OsOrdersHelper::get_order_payment_statuses_list();
		if ( $status && isset( $statuses_list[ $status ] ) ) {
			return $statuses_list[ $status ];
		} else {
			return __( 'Undefined Status', 'latepoint' );
		}
	}

	public static function get_nice_order_status_name( $status ) {
		$statuses_list = OsOrdersHelper::get_order_statuses_list();
		if ( $status && isset( $statuses_list[ $status ] ) ) {
			return $statuses_list[ $status ];
		} else {
			return __( 'Undefined Status', 'latepoint' );
		}
	}

	public static function get_nice_order_fulfillment_status_name( $status ) {
		$statuses_list = OsOrdersHelper::get_fulfillment_statuses_list();
		if ( $status && isset( $statuses_list[ $status ] ) ) {
			return $statuses_list[ $status ];
		} else {
			return __( 'Undefined Status', 'latepoint' );
		}
	}


	public static function unfold_price_breakdown_row( $rows ) {
		$readable_price_breakdown = [];
		foreach ( $rows as $row ) {
			if ( empty( $row ) ) {
				continue;
			}
			if ( ! empty( $row['heading'] ) && ! empty( $row['items'] ) ) {
				$readable_price_breakdown = array_merge( $readable_price_breakdown, self::unfold_price_breakdown_row( $row['items'] ) );
			} elseif ( ! empty( $row['label'] ) ) {
				$label = $row['label'];
				if ( ! empty( $row['note'] ) ) {
					$label .= ' ' . $row['note'];
				}
				if ( ! empty( $row['badge'] ) ) {
					$label .= ' (' . $row['badge'] . ')';
				}
				$readable_price_breakdown[ $label ] = $row['raw_value'] * 1;
			} else {
				$readable_price_breakdown = array_merge( $readable_price_breakdown, self::unfold_price_breakdown_row( $row ) );
			}
		}

		return $readable_price_breakdown;
	}


	public static function generate_confirmation_message( OsOrderModel $order ): string {
		$html = '<div class="summary-status-wrapper summary-status-style-' . esc_attr( OsStepsHelper::get_step_setting_value( 'confirmation', 'order_confirmation_message_style', 'green' ) ) . '">
                    <div class="summary-status-inner">
                        <div class="ss-icon"></div>
                        <div class="ss-title">' . OsStepsHelper::get_step_setting_value( 'confirmation', 'order_confirmation_message_title', esc_html__( 'Appointment Confirmed', 'latepoint' ) ) . '</div>
                        <div class="ss-description">' . OsStepsHelper::get_step_setting_value( 'confirmation', 'order_confirmation_message_content', esc_html__( 'We look forward to seeing you.', 'latepoint' ) ) . '</div>
                        <div class="ss-confirmation-number"><span>' . esc_html__( 'Order #', 'latepoint' ) . '</span><strong>' . esc_html( $order->confirmation_code ) . '</strong></div>
                    </div>
                </div>';

		return $html;
	}


	public static function generate_direct_manage_order_url( OsOrderModel $order, string $for, string $action = 'show' ): string {
		if ( ! in_array( $for, [ 'agent', 'customer' ] ) ) {
			return '';
		}
		$actions = [ 'show', 'list_payments', 'print' ];
		if ( ! in_array( $action, $actions ) ) {
			$action = $actions[0];
		}
		$key = $order->get_key_to_manage_for( $for );
		$url = OsRouterHelper::build_admin_post_link( [ 'manage_order_by_key', $action ], [ 'key' => $key ] );

		return $url;
	}

	public static function get_order_id_and_manage_ability_by_key( string $key ) {
		$order_id = OsMetaHelper::get_order_id_by_meta_value( "key_to_manage_for_agent", $key );
		if ( $order_id ) {
			return [ 'order_id' => $order_id, 'for' => 'agent' ];
		}

		$order_id = OsMetaHelper::get_order_id_by_meta_value( "key_to_manage_for_customer", $key );
		if ( $order_id ) {
			return [ 'order_id' => $order_id, 'for' => 'customer' ];
		}

		return false;
	}

	public static function create_booking_object_from_booking_data_form( array $params ): OsBookingModel {
		$booking = new OsBookingModel();

		if ( ! empty( $params['item_data'] ) ) {
			$item_data = json_decode( base64_decode( $params['item_data'] ), true );
			$booking->set_data( $item_data );
		} else {
			$filtered_params = $params;
			// input fields are formatted in customer preferred format, we need to convert that to database format Y-m-d
			$filtered_params['start_date'] = OsTimeHelper::reformat_date_string( $params['start_date_formatted'], OsSettingsHelper::get_date_format(), 'Y-m-d' );


			if ( isset( $params['start_time']['formatted_value'] ) ) {
				$start_ampm                    = isset( $params['start_time']['ampm'] ) ? $params['start_time']['ampm'] : false;
				$filtered_params['start_time'] = OsTimeHelper::convert_time_to_minutes( $params['start_time']['formatted_value'], $start_ampm );
			}

			// set custom end time/date if it was passed in params
			if ( isset( $params['end_time']['formatted_value'] ) ) {
				$end_ampm                    = isset( $params['end_time']['ampm'] ) ? $params['end_time']['ampm'] : false;
				$filtered_params['end_time'] = OsTimeHelper::convert_time_to_minutes( $params['end_time']['formatted_value'], $end_ampm );
				if ( $filtered_params['end_time'] <= $filtered_params['start_time'] ) {
					// it's next day
					$date_obj                    = new OsWpDateTime( $filtered_params['start_date'] );
					$filtered_params['end_date'] = $date_obj->modify( '+1 day' )->format( 'Y-m-d' );
				} else {
					$filtered_params['end_date'] = $filtered_params['start_date'];
				}
			}
			$booking->set_data( $filtered_params );
			$booking->set_utc_datetimes();
		}

		return $booking;
	}

	public static function loading_tile_for_order_item( string $order_item_id ) {
		return '<div class="order-item-temp-holder" data-order-item-id="' . $order_item_id . '"><div class="oit-avatar"></div><div class="oit-main-info"><div class="oit-title"></div><div class="oit-sub-title"></div></div></div>';
	}

	public static function booking_data_form_for_order_item_id( string $order_item_id, OsBookingModel $booking, string $order_item_variant = LATEPOINT_ITEM_VARIANT_BOOKING, bool $is_folded = true ): string {
		$services = OsServiceHelper::get_allowed_active_services();
		$agents   = OsAgentHelper::get_allowed_active_agents();

		$order_item_id = empty( $order_item_id ) ? OsUtilHelper::generate_form_id() : $order_item_id;

		$extra_css_class = $is_folded ? 'is-folded' : 'is-unfolded';
		$html            = '';
		$html            = '<div class="order-item-booking-data-form-wrapper order-item-booking-data-variant-' . $order_item_variant . ' ' . $extra_css_class . '" 
		data-order-item-variant="' . $order_item_variant . '" 
		data-order-item-id="' . $order_item_id . '" 
		data-booking-id="' . $booking->get_form_id() . '">';
		$html            .= self::loading_tile_for_order_item( $order_item_id );
		switch ( $order_item_variant ) {
			case LATEPOINT_ITEM_VARIANT_BOOKING:
				$html .= OsOrdersHelper::generate_order_item_pill_for_booking( $booking, $order_item_id );
				break;
			case LATEPOINT_ITEM_VARIANT_BUNDLE:
				$html .= OsOrdersHelper::generate_order_item_pill_for_bundle_booking( $booking, $order_item_id );
				break;
		}
		$html .= '<div class="order-booking-data-heading">
									<div class="fold-order-item-wrapper fold-order-item-booking-data-form-btn">
										<div class="fold-order-item-icon"> 
											<i class="latepoint-icon latepoint-icon-chevron-up"></i>
										</div>
										<div class="ob-label">' . ( $booking->is_new_record() ? __( 'New Booking', 'latepoint' ) : __( 'Edit Booking', 'latepoint' ) ) . '</div>
									</div>
									
									<div class="remove-order-item-new-booking-btn remove-order-item-btn" data-os-prompt="' . __( 'Are you sure you want to remove this booking from the order?', 'latepoint' ) . '">
										<i class="latepoint-icon latepoint-icon-trash1"></i>
									</div>
								</div>';
		$html .= '<div class="order-item-booking-data-form-inner">';
		$html .= OsFormHelper::hidden_field( 'order_items[' . $order_item_id . '][variant]', $order_item_variant );
		ob_start();
		include( LATEPOINT_VIEWS_ABSPATH . 'bookings/_booking_data.php' );
		$html .= ob_get_clean();
		$html .= '</div>';
		$html .= '</div>';

		return $html;
	}


	public static function get_orders_for_select() : array {
		$order         = new OsOrderModel();
		$order         = $order->order_by( 'id desc' )->set_limit( 100 )->get_results_as_models();
		$order_options = [];
		foreach ( $order as $order ) {
			$name            = $order->customer->full_name . ' [' . $order->confirmation_code . ' : ' . $order->id . ']';
			$order_options[] = [ 'value' => $order->id, 'label' => esc_html( $name ) ];
		}

		return $order_options;
	}

	public static function generate_order_item_pill_for_bundle( OsBundleModel $bundle, $order_item_id = false, $preselected_booking_id = false ): string {
		$html               = '';
		$order_item_form_id = $order_item_id ? $order_item_id : OsUtilHelper::generate_form_id();
		$html               .= '<div class="order-item-pill order-item-pill-variant-bundle" data-order-item-id="' . $order_item_form_id . '">';
		$html               .= '<input name="order_items[' . $order_item_form_id . '][id]" class="order_item_id" value="' . $order_item_form_id . '" type="hidden"/>';
		$html               .= '<input name="order_items[' . $order_item_form_id . '][variant]" value="' . LATEPOINT_ITEM_VARIANT_BUNDLE . '" type="hidden"/>';
		$html               .= '<input name="order_items[' . $order_item_form_id . '][item_data]" class="order_item_item_data" value="' . base64_encode( wp_json_encode( $bundle->generate_params_for_booking_form() ) ) . '" type="hidden"/>';
		$html               .= '<div class="order-item-pill-inner">';
		$html               .= '<div class="order-item-remove-btn remove-order-item-btn" 
																	data-os-prompt="' . __( 'Are you sure you want to remove this item from the order? All associated appointments will be removed as well.', 'latepoint' ) . '"></div>';
		$html               .= OsBundlesHelper::generate_order_summary_for_bundle( $bundle, $order_item_form_id, $preselected_booking_id );
		$html               .= '<div class="bundle-icon"><i class="latepoint-icon latepoint-icon-chevron-down"></i></div>';
		$html               .= '</div>';
		$html               .= '<div class="order-item-shadow"></div><div class="order-item-shadow"></div>';
		$html               .= '</div>';

		return $html;
	}

	public static function generate_order_item_pill_for_booking( OsBookingModel $booking, $order_item_id = false ): string {
		$html               = '';
		$order_item_form_id = $order_item_id ? $order_item_id : OsUtilHelper::generate_form_id();
		$is_past            = ( ! $booking->is_upcoming() ) ? 'is-past' : '';
		$html               .= '<div class="order-item-pill order-item-pill-variant-' . LATEPOINT_ITEM_VARIANT_BOOKING . ' ' . $is_past . ' status-' . $booking->status . '" data-order-item-id="' . $order_item_form_id . '">';
		$html               .= '<input name="order_items[' . $order_item_form_id . '][id]" class="order_item_id" value="' . $order_item_form_id . '" type="hidden"/>';
		$html               .= '<input name="order_items[' . $order_item_form_id . '][variant]" value="' . LATEPOINT_ITEM_VARIANT_BOOKING . '" type="hidden"/>';
		$html               .= '<div class="order-item-pill-inner">';
		if($booking->recurrence_id){
			$html.= '<div class="order-item-pill-recurring-mark"><div class="popover-message">'.esc_html__('Part of recurring sequence', 'latepoint').'</div><i class="latepoint-icon latepoint-icon-refresh"></i></div>';
		}
		$html               .= '<div class="order-item-remove-btn remove-order-item-btn" 
																	data-os-prompt="' . __( 'Are you sure you want to remove this item from the order?', 'latepoint' ) . '"></div>';
		$html               .= '<div class="booking-item-status-pill"></div>';
		$html               .= OsBookingHelper::generate_summary_for_booking( $booking, false, 'agent' );
		$html               .= '<div class="os-avatar-w" style="background-image: url(' . ( ( $booking->agent->avatar_image_id ) ? $booking->agent->get_avatar_url() : '' ) . ')">';
		if ( ! $booking->agent->avatar_image_id ) {
			$html .= '<div class="os-avatar"><span>' . $booking->agent->get_initials() . '</span></div>';
		}
		$html .= '</div>';
		$html .= '</div>';
		$html .= '</div>';

		return $html;
	}

	public static function generate_order_item_pill_for_bundle_booking( OsBookingModel $booking, $order_item_id ): string {
		$is_past = ( ! $booking->is_upcoming() ) ? 'is-past' : '';
		$html    = '<div class="bundle-booking-item-pill ' . $is_past . ' status-' . $booking->status . '">';
		$html    .= '<div class="bundle-booking-item-pill-inner">';
		$html    .= '<div class="booking-item-status-pill"></div>';
		$html    .= '<div class="bib-datetime">' . $booking->get_nice_start_datetime() . '</div>';
		$html    .= '</div>';
		$html    .= '</div>';

		return $html;
	}

	public static function generate_booking_block_for_bundle_order_item( OsBookingModel $booking, $order_item_id, bool $is_booked = true, $is_preselected = false ): string {
		$html               = '';
		$is_booked_css      = $is_booked ? 'is-booked' : '';
		$is_preselected_css = ( $is_preselected ? 'is-preselected' : '' );

		$html .= '<div class="order-item-variant-bundle-booking ' . $is_booked_css . ' ' . $is_preselected_css . '"
		 data-order-item-variant="' . LATEPOINT_ITEM_VARIANT_BUNDLE . '"
		 data-order-item-id="' . $order_item_id . '"
		 data-booking-id="' . $booking->get_form_id() . '">';
		$html .= '<div class="scheduled-bundle-booking">';
		$html .= self::loading_tile_for_order_item( $order_item_id );
		if ( $is_booked ) {
			$html .= OsOrdersHelper::booking_data_form_for_order_item_id( $order_item_id, $booking, LATEPOINT_ITEM_VARIANT_BUNDLE, ! $is_preselected );
		}
		$html .= '</div>';
		$html .= '<div class="unscheduled-bundle-booking">
							<div class="booking-item-status-pill"></div>
							<div class="bib-label">' . __( 'Schedule now', 'latepoint' ) . '</div>
							<input name="order_items[' . $order_item_id . '][unscheduled_bookings][' . $booking->get_form_id() . '][item_data]" class="unscheduled_booking_item_data" value="' . base64_encode( wp_json_encode( $booking->generate_params_for_booking_form() ) ) . '" type="hidden"/>
						</div>';
		$html .= '</div>';

		return $html;
	}

	public static function generate_price_breakdown_from_params( array $price_breakdown_params ): array {
		$price_breakdown_rows = [];
		$allowed_keys         = [ 'before_subtotal', 'after_subtotal' ];
		foreach ( $allowed_keys as $key ) {
			if ( ! empty( $price_breakdown_params[ $key ] ) ) {
				foreach ( $price_breakdown_params[ $key ] as $row ) {
					if ( ! empty( $row['items'] ) ) {
						$group = [ 'heading' => '', 'items' => [], 'sub_items' => [] ];
						if ( ! empty( $row['heading'] ) ) {
							$group['heading'] = $row['heading'];
						}
						foreach ( $row['items'] as $item ) {
							if ( isset( $item['value'] ) ) {
								$item['raw_value'] = OsMoneyHelper::convert_amount_from_money_input_to_db_format( $item['value'] );
							}
							$group['items'][] = $item;
						}
						if ( ! empty( $row['sub_items'] ) ) {
							foreach ( $row['sub_items'] as $sub_item ) {
								if ( isset( $sub_item['value'] ) ) {
									$sub_item['raw_value'] = OsMoneyHelper::convert_amount_from_money_input_to_db_format( $sub_item['value'] );
								}
								$group['sub_items'][] = $sub_item;
							}
						}
						$price_breakdown_rows[ $key ][] = $group;
					} else {
						if ( isset( $row['value'] ) ) {
							$row['raw_value'] = OsMoneyHelper::convert_amount_from_money_input_to_db_format( $row['value'] );
						}
						$price_breakdown_rows[ $key ][] = $row;
					}
				}
			}
		}

		return $price_breakdown_rows;
	}

	public static function create_order_item_object( array $order_items_param ): OsOrderItemModel {
		$order_item_model          = new OsOrderItemModel();
		$order_item_model->variant = $order_items_param['variant'];
		if ( $order_item_model->is_bundle() ) {
			$order_item_model->item_data = base64_decode( $order_items_param['item_data'] );
		} else {
			// it's a booking variant - so there is only one booking in the array, but still loop for ease of use
			foreach ( $order_items_param['bookings'] as $booking_params ) {
				$booking                     = OsOrdersHelper::create_booking_object_from_booking_data_form( $booking_params );
				$order_item_model->item_data = $booking->generate_item_data();
			}
		}

		return $order_item_model;
	}

	public static function generate_transactions_breakdown_html( OsOrderModel $order ): string {
		$html         = '';
		$transactions = $order->get_transactions();
		if ( $transactions ) {
			$html .= '<div style="margin: 20px 0;">';
			foreach ( $transactions as $transaction ) {
				$html .= '<div style="margin-bottom: 10px;">';
				$html .= '<div>' . esc_html__( 'Payment Portion: ', 'latepoint' ) . '<strong>' . $transaction->get_payment_portion_nice_name() . '</strong></div>';
				$html .= '<div>' . esc_html__( 'Payment Amount: ', 'latepoint' ) . '<strong>' . OsMoneyHelper::format_price( $transaction->amount, true, false ) . '</strong></div>';
				$html .= '</div>';
			}
			$html .= '</div>';
		} else {
			$html .= esc_html__( 'No transactions found.', 'latepoint' );
		}

		return $html;
	}

	public static function generate_summary_breakdown_html( OsOrderModel $order ): string {
		ob_start();
		OsPriceBreakdownHelper::output_price_breakdown( $order->generate_price_breakdown_rows(), true );
		$html = ob_get_clean();

		return $html;
	}

	public static function generate_order_items_html( OsOrderModel $order ) {
		$html        = '';
		$order_items = $order->get_items();
		$html        .= '<table style="width: 100%;">';
		$total_items = count( $order_items );
		$i           = 0;
		foreach ( $order_items as $order_item ) {
			$i ++;
			$html       .= '<tr>';
			$data_style = ( $i < $total_items ) ? 'border-bottom: 1px solid #eee;' : '';
			$html       .= '<td style="' . $data_style . ' padding: 10px 0;">';
			if ( $order_item->is_bundle() ) {
				$bundle          = $order_item->build_original_object_from_item_data();
				$html            .= '<div>' . $bundle->name . '</div>';
				$bundle_services = $bundle->get_services();
				foreach ( $bundle_services as $service ) {
					$qty      = $service->join_attributes['quantity'];
					$qty_html = $qty > 1 ? ' [' . $qty . ']' : '';
					$html     .= '<div style="color: #999; font-size: 14px;">' . esc_html( $service->name . $qty_html ) . '</div>';
				}
			} else {
				$booking = $order_item->build_original_object_from_item_data();
				$html    .= '<div style="font-weight: bold;">' . $booking->service->name . '</div>';
				$html    .= '<div>' . $booking->get_nice_start_datetime() . '</div>';
				$html    .= '<div>' . $booking->agent->get_full_name() . '</div>';
			}
			$html .= '</td>';
			$html .= '</tr>';
		}
		$html .= '</table>';

		return $html;
	}

	public static function extract_agent_emails( OsOrderModel $order ): string {
		$to_emails   = [];
		$order_items = $order->get_items();
		foreach ( $order_items as $order_item ) {
			if ( $order_item->is_bundle() ) {
				$bundle_bookings = OsOrdersHelper::get_bookings_for_order_item( $order_item->id );
				foreach ( $bundle_bookings as $booking ) {
					$to_emails[] = $booking->agent->get_full_name() . ' <' . $booking->agent->email . '>';
				}
			} else {
				// booking
				$booking     = $order_item->build_original_object_from_item_data();
				$to_emails[] = $booking->agent->get_full_name() . ' <' . $booking->agent->email . '>';
			}
		}
		$to_emails = array_unique( $to_emails );

		return implode( ', ', $to_emails );
	}

	public static function extract_agent_full_names( OsOrderModel $order ): string {
		$full_names  = [];
		$order_items = $order->get_items();
		foreach ( $order_items as $order_item ) {
			if ( $order_item->is_bundle() ) {
				$bundle_bookings = OsOrdersHelper::get_bookings_for_order_item( $order_item->id );
				foreach ( $bundle_bookings as $booking ) {
					$full_names[] = $booking->agent->get_full_name();
				}
			} else {
				// booking
				$booking      = $order_item->build_original_object_from_item_data();
				$full_names[] = $booking->agent->get_full_name();
			}
		}
		$full_names = array_unique( $full_names );

		return implode( ', ', $full_names );
	}



	/**
	 * Extract property by name from order. For example, location_id, agent_id...
	 * @param OsOrderModel $order
	 * @param string $property
	 *
	 * @return string
	 */
	public static function extract_property_by_name( OsOrderModel $order, string $property ): string {
		$property_values = [];
		$order_items     = $order->get_items();

		$property_map = [
			'service_ids' => 'service_id',
			'location_ids' => 'location_id',
			'agent_ids' => 'agent_id',
			'bundle_ids' => 'bundle_id'
		];

		if(empty($property_map[$property])){
		   return '';
		}
		$mapped_property = $property_map[$property];

		foreach ( $order_items as $order_item ) {
			if ( $order_item->is_bundle() ) {
				if ($mapped_property == 'bundle_id') {
					$property_values[] = $order_item->get_item_data_value_by_key( 'bundle_id' );
				} else {
					$bundle_bookings = OsOrdersHelper::get_bookings_for_order_item( $order_item->id );
					$property_values = array_merge( $property_values, array_column( $bundle_bookings, $mapped_property ) );
				}
			} else {
				$booking = $order_item->build_original_object_from_item_data();
				if ( ! empty( $booking->$mapped_property ) ) {
					$property_values[] = $booking->$mapped_property;
				}
			}
		}

		return implode( ',', array_unique( $property_values ) );
	}

	public static function check_if_order_invoices_paid_full_balance( $order_id ) {
		$order = new OsOrderModel( $order_id );
		$invoices = new OsInvoiceModel();
		$paid_invoices = $invoices->where(['status' => LATEPOINT_INVOICE_STATUS_PAID, 'order_id' => $order_id])->get_results_as_models();
		$total_paid = 0;
		$updated = false;
		foreach ( $paid_invoices as $invoice ) {
			$total_paid += $invoice->charge_amount;
		}
		if($total_paid > 0){
			$old_order = clone $order;
			if($total_paid < $order->get_total()){
				if($order->payment_status != LATEPOINT_ORDER_PAYMENT_STATUS_PARTIALLY_PAID){
					$updated = $order->update_attributes(['payment_status' => LATEPOINT_ORDER_PAYMENT_STATUS_PARTIALLY_PAID]);
				}
			}else{
				if($order->get_total() > 0 && $order->payment_status != LATEPOINT_ORDER_PAYMENT_STATUS_FULLY_PAID){
					$updated = $order->update_attributes(['payment_status' => LATEPOINT_ORDER_PAYMENT_STATUS_FULLY_PAID]);
				}
			}
			if($updated){
				/**
				 * Order was updated
				 *
				 * @param {OsOrderModel} $order instance of order model after it was updated
				 * @param {OsOrderModel} $old_order instance of order model before it was updated
				 *
				 * @since 5.0.0
				 * @hook latepoint_order_updated
				 *
				 */
				do_action( 'latepoint_order_updated', $order, $old_order );
			}
		}
	}

}