PluginProbe ʕ •ᴥ•ʔ
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress / 5.1.8
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress v5.1.8
5.6.8 5.6.7 5.6.6 5.6.5 5.6.4 5.6.3 5.6.2 5.6.1 5.6.0 5.5.2 5.5.1 5.5.0 5.4.2 trunk 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.1.91 5.1.92 5.1.93 5.1.94 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1
latepoint / lib / helpers / booking_helper.php
latepoint / lib / helpers Last commit date
activities_helper.php 1 year ago agent_helper.php 1 year ago auth_helper.php 1 year ago blocks_helper.php 1 year ago booking_helper.php 1 year ago bricks_helper.php 1 year ago bundles_helper.php 1 year ago calendar_helper.php 1 year ago carts_helper.php 1 year ago connector_helper.php 1 year ago csv_helper.php 1 year ago customer_helper.php 1 year ago database_helper.php 1 year ago debug_helper.php 1 year ago defaults_helper.php 1 year ago elementor_helper.php 1 year ago email_helper.php 1 year ago encrypt_helper.php 1 year ago events_helper.php 1 year ago form_helper.php 1 year ago icalendar_helper.php 1 year ago image_helper.php 1 year ago invoices_helper.php 1 year ago license_helper.php 1 year ago location_helper.php 1 year ago marketing_systems_helper.php 1 year ago meeting_systems_helper.php 1 year ago menu_helper.php 1 year ago meta_helper.php 1 year ago migrations_helper.php 1 year ago money_helper.php 1 year ago notifications_helper.php 1 year ago order_intent_helper.php 1 year ago orders_helper.php 1 year ago pages_helper.php 1 year ago params_helper.php 1 year ago payments_helper.php 1 year ago price_breakdown_helper.php 1 year ago process_jobs_helper.php 1 year ago processes_helper.php 1 year ago replacer_helper.php 1 year ago resource_helper.php 1 year ago roles_helper.php 1 year ago router_helper.php 1 year ago service_helper.php 1 year ago sessions_helper.php 1 year ago settings_helper.php 1 year ago shortcodes_helper.php 1 year ago sms_helper.php 1 year ago steps_helper.php 1 year ago stripe_connect_helper.php 1 year ago styles_helper.php 1 year ago support_topics_helper.php 1 year ago time_helper.php 1 year ago timeline_helper.php 1 year ago transaction_helper.php 1 year ago transaction_intent_helper.php 1 year ago util_helper.php 1 year ago version_specific_updates_helper.php 1 year ago whatsapp_helper.php 1 year ago work_periods_helper.php 1 year ago wp_datetime.php 1 year ago wp_user_helper.php 1 year ago
booking_helper.php
1728 lines
1 <?php
2
3 class OsBookingHelper {
4
5
6 /**
7 * @param OsBookingModel $booking
8 *
9 * @return mixed|void
10 *
11 * Returns full amount to charge in database format 1999.0000
12 *
13 */
14 public static function calculate_full_amount_for_booking( OsBookingModel $booking ) {
15 if ( ! $booking->service_id ) {
16 return 0;
17 }
18 $amount = self::calculate_full_amount_for_service( $booking );
19 $amount = apply_filters( 'latepoint_calculate_full_amount_for_booking', $amount, $booking );
20 $amount = OsMoneyHelper::pad_to_db_format( $amount );
21
22 return $amount;
23 }
24
25
26 /**
27 * @param OsBookingModel $booking
28 *
29 * @return mixed|void
30 *
31 */
32 public static function calculate_full_amount_for_service( OsBookingModel $booking ) {
33 if ( ! $booking->service_id ) {
34 return 0;
35 }
36 $service = new OsServiceModel( $booking->service_id );
37 $amount_for_service = $service->get_full_amount_for_duration( $booking->duration );
38 $amount_for_service = apply_filters( 'latepoint_full_amount_for_service', $amount_for_service, $booking );
39
40 return $amount_for_service;
41 }
42
43
44 /**
45 * @param OsBookingModel $booking
46 *
47 * @return mixed|void
48 *
49 * Returns deposit amount to charge in database format 1999.0000
50 *
51 */
52 public static function calculate_deposit_amount_to_charge( OsBookingModel $booking ) {
53 if ( ! $booking->service_id ) {
54 return 0;
55 }
56 $service = new OsServiceModel( $booking->service_id );
57 $amount = $service->get_deposit_amount_for_duration( $booking->duration );
58 $amount = apply_filters( 'latepoint_deposit_amount_for_service', $amount, $booking );
59 $amount = OsMoneyHelper::pad_to_db_format( $amount );
60
61 return $amount;
62 }
63
64
65 /**
66 * @param array $item_data
67 *
68 * @return OsBookingModel
69 */
70 public static function build_booking_model_from_item_data( array $item_data ): OsBookingModel {
71 $booking = new OsBookingModel();
72 if ( $item_data['id'] ) {
73 $booking = $booking->load_by_id( $item_data['id'] );
74 if ( ! $booking ) {
75 $booking = new OsBookingModel();
76 }
77 }
78 $booking->set_data( $item_data );
79 // get buffers from service and set to booking object
80 if ( ! isset( $item_data['buffer_before'] ) && ! isset( $item_data['buffer_after'] ) ) {
81 $booking->set_buffers();
82 }
83 if ( empty( $booking->end_time ) ) {
84 $booking->calculate_end_date_and_time();
85 }
86 if ( empty( $booking->end_date ) ) {
87 $booking->calculate_end_date();
88 }
89 $booking->set_utc_datetimes();
90
91 return $booking;
92 }
93
94 public static function get_booking_id_and_manage_ability_by_key( string $key ) {
95 $booking_id = OsMetaHelper::get_booking_id_by_meta_value( "key_to_manage_for_agent", $key );
96 if ( $booking_id ) {
97 return [ 'booking_id' => $booking_id, 'for' => 'agent' ];
98 }
99
100 $booking_id = OsMetaHelper::get_booking_id_by_meta_value( "key_to_manage_for_customer", $key );
101 if ( $booking_id ) {
102 return [ 'booking_id' => $booking_id, 'for' => 'customer' ];
103 }
104
105 return false;
106 }
107
108 public static function is_action_allowed( string $action, OsBookingModel $booking, string $key = '' ) {
109 $is_allowed = false;
110 if ( empty( $booking->id ) ) {
111 return false;
112 }
113 if ( ! in_array( $action, [ 'cancel', 'reschedule' ] ) ) {
114 return false;
115 }
116 $action_result = false;
117 switch ( $action ) {
118 case 'cancel':
119 $action_result = OsCustomerHelper::can_cancel_booking( $booking );
120 break;
121 case 'reschedule':
122 $action_result = OsCustomerHelper::can_reschedule_booking( $booking );
123 break;
124 }
125 if ( ! empty( $key ) ) {
126 // key is passed, check if allowed through key
127 $agent_key_meta = OsMetaHelper::get_booking_meta_by_key( 'key_to_manage_for_agent', $booking->id );
128 $customer_key_meta = OsMetaHelper::get_booking_meta_by_key( 'key_to_manage_for_customer', $booking->id );
129 if ( $key == $agent_key_meta ) {
130 // agent can do everything, no need to check for action
131 $is_allowed = true;
132 } elseif ( $key == $customer_key_meta ) {
133 // customer
134 $is_allowed = $action_result;
135 }
136 } elseif ( OsAuthHelper::get_logged_in_customer_id() == $booking->customer_id ) {
137 $is_allowed = $action_result;
138 }
139
140 return $is_allowed;
141 }
142
143 public static function generate_add_to_calendar_links( OsBookingModel $booking, $key = false ): string {
144 $html = '<div class="add-to-calendar-types">
145 <div class="atc-heading-wrapper">
146 <div class="atc-heading">' . esc_html__( 'Calendar Type', 'latepoint' ) . '</div>
147 <div class="close-calendar-types"></div>
148 </div>
149 <a href="' . esc_url($booking->get_ical_download_link( $key )) . '" target="_blank" class="atc-type atc-type-apple">
150 <div class="atc-type-image"></div>
151 <div class="atc-type-name">' . esc_html__( 'Apple Calendar', 'latepoint' ) . '</div>
152 </a>
153 <a href="' . esc_url($booking->get_url_for_add_to_calendar_button( 'google' )) . '" target="_blank" class="atc-type atc-type-google">
154 <div class="atc-type-image"></div>
155 <div class="atc-type-name">' . esc_html__( 'Google Calendar', 'latepoint' ) . '</div>
156 </a>
157 <a href="' . esc_url($booking->get_url_for_add_to_calendar_button( 'outlook' )) . '" target="_blank" class="atc-type atc-type-outlook">
158 <div class="atc-type-image"></div>
159 <div class="atc-type-name">' . esc_html__( 'Outlook.com', 'latepoint' ) . '</div>
160 </a>
161 <a href="' . esc_url($booking->get_url_for_add_to_calendar_button( 'outlook' )) . '" target="_blank" class="atc-type atc-type-office-365">
162 <div class="atc-type-image"></div>
163 <div class="atc-type-name">' . esc_html__( 'Microsoft 365', 'latepoint' ) . '</div>
164 </a>
165 </div>';
166
167 return $html;
168 }
169
170
171 public static function get_bookings_for_select( $should_be_in_future = false ) {
172 $bookings = new OsBookingModel();
173 if ( $should_be_in_future ) {
174 $bookings = $bookings->should_be_in_future();
175 }
176 $bookings = $bookings->order_by( 'id desc' )->set_limit( 100 )->get_results_as_models();
177 $bookings_options = [];
178 foreach ( $bookings as $booking ) {
179 $name = $booking->service->name . ', ' . $booking->agent->full_name . ', ' . $booking->customer->full_name . ' [' . $booking->booking_code . ' : ' . $booking->id . ']';
180 $bookings_options[] = [ 'value' => $booking->id, 'label' => esc_html( $name ) ];
181 }
182
183 return $bookings_options;
184 }
185
186 /**
187 *
188 * Determine whether to show
189 *
190 * @param $rows
191 *
192 * @return bool
193 */
194 public static function is_breakdown_free( $rows ) {
195 return ( ( empty( $rows['subtotal']['raw_value'] ) || ( (float) $rows['subtotal']['raw_value'] <= 0 ) ) && ( empty( $rows['total']['raw_value'] ) || ( (float) $rows['total']['raw_value'] <= 0 ) ) );
196 }
197
198 public static function output_price_breakdown( $rows ) {
199 foreach ( $rows['before_subtotal'] as $row ) {
200 self::output_price_breakdown_row( $row );
201 }
202 // if there is nothing between subtotal and total - don't show subtotal as it will be identical to total
203 if ( ! empty( $rows['after_subtotal'] ) ) {
204 if ( ! empty( $rows['subtotal'] ) ) {
205 echo '<div class="subtotal-separator"></div>';
206 self::output_price_breakdown_row( $rows['subtotal'] );
207 }
208 foreach ( $rows['after_subtotal'] as $row ) {
209 self::output_price_breakdown_row( $row );
210 }
211 }
212 if ( ! empty( $rows['total'] ) ) {
213 self::output_price_breakdown_row( $rows['total'] );
214 }
215 if ( ! empty( $rows['payments'] ) ) {
216 foreach ( $rows['payments'] as $row ) {
217 self::output_price_breakdown_row( $row );
218 }
219 }
220 if ( ! empty( $rows['balance'] ) ) {
221 self::output_price_breakdown_row( $rows['balance'] );
222 }
223 }
224
225 public static function output_price_breakdown_row( $row ) {
226 if ( ! empty( $row['items'] ) ) {
227 if ( ! empty( $row['heading'] ) ) {
228 echo '<div class="summary-box-heading"><div class="sbh-item">' . esc_html($row['heading']) . '</div><div class="sbh-line"></div></div>';
229 }
230 foreach ( $row['items'] as $row_item ) {
231 self::output_price_breakdown_row( $row_item );
232 }
233 } else {
234 $extra_class = '';
235 if ( isset( $row['style'] ) && $row['style'] == 'strong' ) {
236 $extra_class .= ' spi-strong';
237 }
238 if ( isset( $row['style'] ) && $row['style'] == 'total' ) {
239 $extra_class .= ' spi-total';
240 }
241 if ( isset( $row['type'] ) && $row['type'] == 'credit' ) {
242 $extra_class .= ' spi-positive';
243 }
244 ?>
245 <div class="summary-price-item-w <?php echo esc_attr($extra_class); ?>">
246 <div class="spi-name">
247 <?php echo $row['label']; ?>
248 <?php if ( ! empty( $row['note'] ) ) {
249 echo '<span class="pi-note">' . esc_html($row['note']) . '</span>';
250 } ?>
251 <?php if ( ! empty( $row['badge'] ) ) {
252 echo '<span class="pi-badge">' . esc_html($row['badge']) . '</span>';
253 } ?>
254 </div>
255 <div class="spi-price"><?php echo esc_html($row['value']); ?></div>
256 </div>
257 <?php
258 }
259 }
260
261 public static function output_price_breakdown_row_as_input_field( $row, $base_name ) {
262 $field_name = $base_name . '[' . OsUtilHelper::random_text( 'alnum', 8 ) . ']';
263 if ( ! empty( $row['items'] ) ) {
264 echo OsFormHelper::hidden_field( $field_name . '[heading]', $row['heading'] ?? '' );
265 foreach ( $row['items'] as $row_item ) {
266 self::output_price_breakdown_row_as_input_field( $row_item, $field_name . '[items]' );
267 }
268 } else {
269 $wrapper_class = ( $row['raw_value'] < 0 ) ? [ 'class' => 'green-value-input' ] : [];
270 $label = $row['label'] ?? '';
271 if ( ! empty( $row['note'] ) ) {
272 $label .= ' ' . $row['note'];
273 }
274 echo OsFormHelper::money_field( $field_name . '[value]', $label, $row['raw_value'], [ 'theme' => 'right-aligned' ], [], $wrapper_class );
275 echo OsFormHelper::hidden_field( $field_name . '[label]', $row['label'] ?? '' );
276 echo OsFormHelper::hidden_field( $field_name . '[style]', $row['style'] ?? '' );
277 echo OsFormHelper::hidden_field( $field_name . '[type]', $row['type'] ?? '' );
278 echo OsFormHelper::hidden_field( $field_name . '[note]', $row['note'] ?? '' );
279 echo OsFormHelper::hidden_field( $field_name . '[badge]', $row['badge'] ?? '' );
280 }
281 if ( ! empty( $row['sub_items'] ) ) {
282 foreach ( $row['sub_items'] as $row_item ) {
283 self::output_price_breakdown_row_as_input_field( $row_item, $field_name . '[sub_items]' );
284 }
285 }
286 }
287
288 /**
289 * @param \LatePoint\Misc\Filter $filter
290 * @param bool $accessed_from_backend
291 *
292 * @return array
293 */
294 public static function get_blocked_periods_grouped_by_day( \LatePoint\Misc\Filter $filter, bool $accessed_from_backend = false ): array {
295 $grouped_blocked_periods = [];
296
297 if ( $filter->date_from ) {
298 $date_from = OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from );
299 $date_to = ( $filter->date_to ) ? OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_to ) : OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from );
300
301 # Loop through days to fill in days that might have no bookings
302 for ( $day = clone $date_from; $day->format( 'Y-m-d' ) <= $date_to->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) {
303 $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ] = [];
304 }
305 }
306 if ( ! $accessed_from_backend ) {
307 $today = new OsWpDateTime( 'today' );
308 $earliest_possible_booking = OsSettingsHelper::get_settings_value( 'earliest_possible_booking', false );
309 $block_end_datetime = OsTimeHelper::now_datetime_object();
310 if ( $earliest_possible_booking ) {
311 try {
312 $block_end_datetime->modify( $earliest_possible_booking );
313 } catch ( Exception $e ) {
314 $block_end_datetime = OsTimeHelper::now_datetime_object();
315 }
316 }
317 for ( $day = clone $today; $day->format( 'Y-m-d' ) <= $block_end_datetime->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) {
318 // loop days from now to earliest possible booking and block timeslots if these days were actually requested
319 if ( isset( $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ] ) ) {
320 $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ][] = new \LatePoint\Misc\BlockedPeriod( [
321 'start_time' => 0,
322 'end_time' => ( $day->format( 'Y-m-d' ) < $block_end_datetime->format( 'Y-m-d' ) ) ? 24 * 60 : OsTimeHelper::convert_datetime_to_minutes( $block_end_datetime ),
323 'start_date' => $day->format( 'Y-m-d' ),
324 'end_date' => $day->format( 'Y-m-d' )
325 ] );
326 }
327 }
328 }
329
330 $grouped_blocked_periods = apply_filters( 'latepoint_blocked_periods_for_range', $grouped_blocked_periods, $filter );
331
332 return $grouped_blocked_periods;
333 }
334
335 /**
336 * @param \LatePoint\Misc\Filter $filter
337 *
338 * @return array
339 */
340 public static function get_booked_periods_grouped_by_day( \LatePoint\Misc\Filter $filter ): array {
341 $booked_periods = self::get_booked_periods( $filter );
342
343 $grouped_booked_periods = [];
344 if ( $filter->date_from ) {
345 $date_from = OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from );
346 $date_to = ( $filter->date_to ) ? OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_to ) : OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from );
347
348 # Loop through days to fill in days that might have no bookings
349 for ( $day = clone $date_from; $day->format( 'Y-m-d' ) <= $date_to->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) {
350 $grouped_booked_periods[ $day->format( 'Y-m-d' ) ] = [];
351 }
352 foreach ( $booked_periods as $booked_period ) {
353 $grouped_booked_periods[ $booked_period->start_date ][] = $booked_period;
354 // if event spans multiple days - add to other days as well
355 if ( $booked_period->end_date && ( $booked_period->start_date != $booked_period->end_date ) ) {
356 $grouped_booked_periods[ $booked_period->end_date ][] = $booked_period;
357 }
358 }
359 }
360
361 return $grouped_booked_periods;
362 }
363
364 /**
365 * @param \LatePoint\Misc\Filter $filter
366 *
367 * @return \LatePoint\Misc\BookedPeriod[]
368 */
369 public static function get_booked_periods( \LatePoint\Misc\Filter $filter ): array {
370
371
372 $bookings = self::get_bookings( $filter, true );
373 $booked_periods = [];
374
375 foreach ( $bookings as $booking ) {
376 $booked_periods[] = \LatePoint\Misc\BookedPeriod::create_from_booking_model( $booking );
377 }
378
379
380 if($filter->consider_cart_items){
381 $cart = OsCartsHelper::get_or_create_cart();
382 $bookings_in_cart = $cart->get_bookings_from_cart_items();
383
384 foreach ( $bookings_in_cart as $cart_booking ) {
385 $booked_periods[] = \LatePoint\Misc\BookedPeriod::create_from_booking_model( $cart_booking );
386 }
387 }
388
389 // TODO Update all filters to accept new "filter" variable (In Google Calendar addon)
390 $booked_periods = apply_filters( 'latepoint_get_booked_periods', $booked_periods, $filter );
391
392 return $booked_periods;
393 }
394
395
396 /**
397 * @param \LatePoint\Misc\Filter $filter
398 * @param bool $as_models
399 *
400 * @return array
401 */
402 public static function get_bookings( \LatePoint\Misc\Filter $filter, bool $as_models = false ): array {
403 $bookings = new OsBookingModel();
404 if ( $filter->date_from ) {
405 if ( $filter->date_from && $filter->date_to ) {
406 # both start and end date provided - means it's a range
407 $bookings->where( [ 'start_date >=' => $filter->date_from, 'start_date <=' => $filter->date_to ] );
408 } else {
409 # only start_date provided - means it's a specific date requested
410 $bookings->where( [ 'start_date' => $filter->date_from ] );
411 }
412 }
413
414
415 if ( $filter->connections ) {
416 $connection_conditions = [];
417 foreach ( $filter->connections as $connection ) {
418 $connection_conditions[] = [
419 'AND' =>
420 [
421 'agent_id' => $connection->agent_id,
422 'service_id' => $connection->service_id,
423 'location_id' => $connection->location_id
424 ]
425 ];
426 }
427 $bookings->where( [ 'OR' => $connection_conditions ] );
428 } else {
429 if ( $filter->agent_id ) {
430 $bookings->where( [ 'agent_id' => $filter->agent_id ] );
431 }
432 if ( $filter->location_id ) {
433 $bookings->where( [ 'location_id' => $filter->location_id ] );
434 }
435 if ( $filter->service_id ) {
436 $bookings->where( [ 'service_id' => $filter->service_id ] );
437 }
438 }
439 if ( $filter->statuses ) {
440 $bookings->where( [ 'status' => $filter->statuses ] );
441 }
442 if ( $filter->exclude_booking_ids ) {
443 $bookings->where( [ 'id NOT IN' => $filter->exclude_booking_ids ] );
444 }
445 $bookings->order_by( 'start_time asc, end_time asc, service_id asc' );
446 $bookings = ( $as_models ) ? $bookings->get_results_as_models() : $bookings->get_results();
447
448 // make sure to return empty array if nothing is found
449 if ( empty( $bookings ) ) {
450 $bookings = [];
451 }
452
453 return $bookings;
454 }
455
456 public static function generate_ical_event_string( $booking ) {
457 // translators: %1$s is agent name, %2$s is service name
458 $booking_description = sprintf( __( 'Appointment with %1$s for %2$s', 'latepoint' ), $booking->agent->full_name, $booking->service->name );
459
460 $ics = new ICS( array(
461 'location' => $booking->location->full_address,
462 'description' => '',
463 'dtstart' => $booking->format_start_date_and_time_for_google(),
464 'dtend' => $booking->format_end_date_and_time_for_google(),
465 'summary' => $booking_description,
466 'url' => get_site_url()
467 ) );
468
469 return $ics->to_string();
470 }
471
472 /**
473 * @param \LatePoint\Misc\BookingRequest $booking_request
474 *
475 * @return bool
476 *
477 * Checks if requested booking slot is available, loads work periods and booked periods from database and checks availability against them
478 */
479 public static function is_booking_request_available( \LatePoint\Misc\BookingRequest $booking_request, $settings = [] ): bool {
480 $requested_date = new OsWpDateTime( $booking_request->start_date );
481 $resources = OsResourceHelper::get_resources_grouped_by_day( $booking_request, $requested_date, $requested_date, $settings );
482 if ( empty( $resources[ $requested_date->format( 'Y-m-d' ) ] ) ) {
483 return false;
484 }
485 $is_available = false;
486 foreach ( $resources[ $requested_date->format( 'Y-m-d' ) ] as $resource ) {
487 foreach ( $resource->slots as $slot ) {
488 if ( $slot->start_time == $booking_request->start_time && $slot->can_accomodate( $booking_request->total_attendees ) ) {
489 $is_available = true;
490 }
491 if ( $is_available ) {
492 break;
493 }
494 }
495 if ( $is_available ) {
496 break;
497 }
498 }
499
500 return $is_available;
501 }
502
503 /**
504 *
505 * Checks if two bookings are part of the same group appointment
506 *
507 * @param bool|OsBookingModel $booking
508 * @param bool|OsBookingModel $compare_booking
509 *
510 * @return bool
511 */
512 public static function check_if_group_bookings( $booking, $compare_booking ): bool {
513 if ( $booking && $compare_booking && ( $compare_booking->start_time == $booking->start_time ) && ( $compare_booking->end_time == $booking->end_time ) && ( $compare_booking->service_id == $booking->service_id ) && ( $compare_booking->location_id == $booking->location_id ) ) {
514 return true;
515 } else {
516 return false;
517 }
518 }
519
520
521 public static function process_actions_after_save( $booking_id ) {
522 }
523
524 /**
525 * @param DateTime $start_date
526 * @param DateTime $end_date
527 * @param \LatePoint\Misc\BookingRequest $booking_request
528 * @param \LatePoint\Misc\BookingResource[] $resources
529 * @param array $settings
530 *
531 * @return string
532 * @throws Exception
533 */
534 public static function get_quick_availability_days( DateTime $start_date, DateTime $end_date, \LatePoint\Misc\BookingRequest $booking_request, array $resources = [], array $settings = [] ) {
535 $default_settings = [
536 'work_boundaries' => false,
537 'exclude_booking_ids' => []
538 ];
539 $settings = array_merge( $default_settings, $settings );
540
541 $html = '';
542
543 if ( ! $resources ) {
544 $resources = OsResourceHelper::get_resources_grouped_by_day( $booking_request, $start_date, $end_date, $settings );
545 }
546 if ( ! $settings['work_boundaries'] ) {
547 $settings['work_boundaries'] = OsResourceHelper::get_work_boundaries_for_groups_of_resources( $resources );
548 }
549
550 for ( $day_date = clone $start_date; $day_date <= $end_date; $day_date->modify( '+1 day' ) ) {
551 // first day of month, output month name
552 if ( $day_date->format( 'j' ) == '1' ) {
553 $html .= '<div class="ma-month-label">' . OsUtilHelper::get_month_name_by_number( $day_date->format( 'n' ) ) . '</div>';
554 }
555 $html .= '<div class="ma-day ma-day-number-' . $day_date->format( 'N' ) . '">';
556 $html .= '<div class="ma-day-info">';
557 $html .= '<span class="ma-day-number">' . $day_date->format( 'j' ) . '</span>';
558 $html .= '<span class="ma-day-weekday">' . OsUtilHelper::get_weekday_name_by_number( $day_date->format( 'N' ), true ) . '</span>';
559 $html .= '</div>';
560 $html .= OsTimelineHelper::availability_timeline( $booking_request, $settings['work_boundaries'], $resources[ $day_date->format( 'Y-m-d' ) ], [ 'book_on_click' => false ] );
561 $html .= '</div>';
562 }
563
564 return $html;
565 }
566
567 public static function count_pending_bookings() {
568 $bookings = new OsBookingModel();
569
570 return $bookings->filter_allowed_records()->where( [ 'status IN' => OsBookingHelper::get_booking_statuses_for_pending_page() ] )->count();
571 }
572
573
574 public static function generate_bundles_folder(): void {
575 $bundles_model = new OsBundleModel();
576 $bundles = $bundles_model->should_be_active()->should_not_be_hidden()->get_results_as_models();
577
578 if ( $bundles ) {
579 ?>
580 <div class="os-item-category-w os-items os-as-rows os-animated-child">
581 <div class="os-item-category-info-w os-item os-animated-self with-plus">
582 <div class="os-item-category-info os-item-i" tabindex="0">
583 <div class="os-item-img-w"><i class="latepoint-icon latepoint-icon-shopping-bag"></i></div>
584 <div class="os-item-name-w">
585 <div class="os-item-name"><?php echo esc_html__( 'Bundle & Save', 'latepoint' ); ?></div>
586 </div>
587 <?php if ( count( $bundles ) ) { ?>
588 <div class="os-item-child-count">
589 <span><?php echo count( $bundles ); ?></span> <?php esc_html_e( 'Bundles', 'latepoint' ); ?>
590 </div>
591 <?php } ?>
592 </div>
593 </div>
594 <div class="os-bundles os-animated-parent os-items os-as-rows os-selectable-items">
595 <?php
596 foreach ( $bundles as $bundle ) { ?>
597 <div class="os-animated-child os-item os-selectable-item <?php echo ( $bundle->charge_amount ) ? 'os-priced-item' : ''; ?> <?php if ( $bundle->short_description ) { echo 'with-description'; } ?>"
598 tabindex="0"
599 data-item-price="<?php echo esc_attr($bundle->charge_amount); ?>"
600 data-priced-item-type="bundle"
601 data-summary-field-name="bundle"
602 data-summary-value="<?php echo esc_attr( $bundle->name ); ?>"
603 data-item-id="<?php echo esc_attr($bundle->id); ?>"
604 data-cart-item-item-data-key="bundle_id"
605 data-os-call-func="latepoint_bundle_selected">
606 <div class="os-service-selector os-item-i os-animated-self"
607 data-bundle-id="<?php echo esc_attr($bundle->id); ?>">
608 <span class="os-item-img-w"><i class="latepoint-icon latepoint-icon-shopping-bag"></i></span>
609 <span class="os-item-name-w">
610 <span class="os-item-name"><?php echo esc_html($bundle->name); ?></span>
611 <?php if ( $bundle->short_description ) { ?>
612 <span class="os-item-desc"><?php echo wp_kses_post($bundle->short_description); ?></span>
613 <?php } ?>
614 </span>
615
616 <?php if ( $bundle->charge_amount > 0 ) { ?>
617 <span class="os-item-price-w">
618 <span class="os-item-price">
619 <?php echo esc_html(OsMoneyHelper::format_price($bundle->charge_amount)); ?>
620 </span>
621 </span>
622 <?php } ?>
623 </div>
624 </div>
625 <?php
626 }
627 ?>
628 </div>
629 </div>
630 <?php
631 }
632 }
633
634 public static function generate_services_list( $services = false, $preselected_service = false ) {
635 if ( $services && is_array( $services ) && ! empty( $services ) ) { ?>
636 <div class="os-services os-animated-parent os-items os-as-rows os-selectable-items">
637 <?php foreach ( $services as $service ) {
638 // if service is preselected - only output that service, skip the rest
639 if ( $preselected_service && $service->id != $preselected_service->id ) {
640 continue;
641 }
642 $service_durations = $service->get_all_durations_arr();
643 $is_priced = ( ! ( count( $service_durations ) > 1 ) && $service->charge_amount ) ? true : false;
644 ?>
645 <div class="os-animated-child os-item os-selectable-item <?php echo ( $preselected_service && $service->id == $preselected_service->id ) ? 'selected is-preselected' : ''; ?> <?php echo ( $is_priced ) ? 'os-priced-item' : ''; ?> <?php if ( $service->short_description ) { echo 'with-description'; } ?>"
646 tabindex="0"
647 data-item-price="<?php echo esc_attr($service->charge_amount); ?>"
648 data-priced-item-type="service"
649 data-summary-field-name="service"
650 data-summary-value="<?php echo esc_attr( $service->name ); ?>"
651 data-item-id="<?php echo esc_attr($service->id); ?>"
652 data-cart-item-item-data-key="service_id"
653 data-os-call-func="latepoint_service_selected"
654 data-id-holder=".latepoint_service_id">
655 <div class="os-service-selector os-item-i os-animated-self" data-service-id="<?php echo esc_attr($service->id); ?>">
656 <?php if ( $service->selection_image_id ) { ?>
657 <span class="os-item-img-w" style="background-image: url(<?php echo esc_url($service->selection_image_url); ?>);"></span>
658 <?php } ?>
659 <span class="os-item-name-w">
660 <span class="os-item-name"><?php echo esc_html($service->name); ?></span>
661 <?php if ( $service->short_description ) { ?>
662 <span class="os-item-desc"><?php echo wp_kses_post($service->short_description); ?></span>
663 <?php } ?>
664 </span>
665 <?php if ( $service->price_min > 0 ) { ?>
666 <span class="os-item-price-w">
667 <span class="os-item-price">
668 <?php echo esc_html($service->price_min_formatted); ?>
669 </span>
670 <?php if ( $service->price_min != $service->price_max ) { ?>
671 <span class="os-item-price-label"><?php esc_html_e( 'Starts From', 'latepoint' ); ?></span>
672 <?php } ?>
673 </span>
674 <?php } ?>
675 </div>
676 </div>
677 <?php } ?>
678 </div>
679 <?php }
680 }
681
682 public static function generate_services_bundles_and_categories_list( $parent_id = false, array $settings = [] ) {
683 $default_settings = [
684 'show_service_categories_arr' => false,
685 'show_services_arr' => false,
686 'preselected_service' => false,
687 'preselected_category' => false,
688 ];
689 $settings = array_merge( $default_settings, $settings );
690
691 if ( $settings['preselected_service'] ) {
692 OsBookingHelper::generate_services_list( [ $settings['preselected_service'] ], $settings['preselected_service'] );
693
694 return;
695 }
696
697 $service_categories = new OsServiceCategoryModel();
698 $args = array();
699 if ( $settings['show_service_categories_arr'] && is_array( $settings['show_service_categories_arr'] ) ) {
700 if ( $parent_id ) {
701 $service_categories->where( [ 'parent_id' => $parent_id ] );
702 } else {
703 if ( $settings['preselected_category'] ) {
704 $service_categories->where( [ 'id' => $settings['preselected_category'] ] );
705 } else {
706 $service_categories->where_in( 'id', $settings['show_service_categories_arr'] );
707 $service_categories->where( [
708 'parent_id' => [
709 'OR' => [
710 'IS NULL',
711 ' NOT IN' => $settings['show_service_categories_arr']
712 ]
713 ]
714 ] );
715 }
716 }
717 } else {
718 if ( $settings['preselected_category'] ) {
719 $service_categories->where( [ 'id' => $settings['preselected_category'] ] );
720 } else {
721 $args['parent_id'] = $parent_id ? $parent_id : 'IS NULL';
722 }
723 }
724 $service_categories = $service_categories->where( $args )->order_by( 'order_number asc' )->get_results_as_models();
725
726 $main_parent_class = ( $parent_id ) ? 'os-animated-parent' : 'os-item-categories-main-parent os-animated-parent';
727 if ( ! $settings['preselected_category'] ) {
728 echo '<div class="os-item-categories-holder ' . esc_attr($main_parent_class) . '">';
729 }
730
731 // generate services that have no category
732 if ( $parent_id == false && $settings['preselected_category'] == false ) { ?>
733 <?php
734 $services_without_category = new OsServiceModel();
735 if ( $settings['show_services_arr'] ) {
736 $services_without_category->where_in( 'id', $settings['show_services_arr'] );
737 }
738 $services_without_category = $services_without_category->where( [ 'category_id' => 0 ] )->should_be_active()->get_results_as_models();
739 if ( $services_without_category ) {
740 OsBookingHelper::generate_services_list( $services_without_category, false );
741 }
742 }
743
744 if ( is_array( $service_categories ) ) {
745 foreach ( $service_categories as $service_category ) { ?>
746 <?php
747 $services = [];
748 $category_services = $service_category->get_active_services();
749 if ( is_array( $category_services ) ) {
750 // if show selected services restriction is set - filter
751 if ( $settings['show_services_arr'] ) {
752 foreach ( $category_services as $category_service ) {
753 if ( in_array( $category_service->id, $settings['show_services_arr'] ) ) {
754 $services[] = $category_service;
755 }
756 }
757 } else {
758 $services = $category_services;
759 }
760 }
761 $child_categories = new OsServiceCategoryModel();
762 $count_child_categories = $child_categories->where( [ 'parent_id' => $service_category->id ] )->count();
763 // show only if it has either at least one child category or service
764 if ( $count_child_categories || count( $services ) ) {
765 // preselected category, just show contents, not the wrapper
766 if ( $service_category->id == $settings['preselected_category'] ) {
767 OsBookingHelper::generate_services_list( $services, false );
768 OsBookingHelper::generate_services_bundles_and_categories_list( $service_category->id, array_merge( $settings, [ 'preselected_category' => false ] ) );
769 } else { ?>
770 <div class="os-item-category-w os-items os-as-rows os-animated-child"
771 data-id="<?php echo esc_attr($service_category->id); ?>">
772 <div class="os-item-category-info-w os-item os-animated-self with-plus">
773 <div class="os-item-category-info os-item-i">
774 <div class="os-item-img-w"
775 style="background-image: url(<?php echo esc_url($service_category->selection_image_url); ?>);"></div>
776 <div class="os-item-name-w">
777 <div class="os-item-name"><?php echo esc_html($service_category->name); ?></div>
778 </div>
779 <?php if ( count( $services ) ) { ?>
780 <div class="os-item-child-count">
781 <span><?php echo count( $services ); ?></span> <?php esc_html_e( 'Services', 'latepoint' ); ?>
782 </div>
783 <?php } ?>
784 </div>
785 </div>
786 <?php OsBookingHelper::generate_services_list( $services, false ); ?>
787 <?php OsBookingHelper::generate_services_bundles_and_categories_list( $service_category->id, array_merge( $settings, [ 'preselected_category' => false ] ) ); ?>
788 </div><?php
789 }
790 }
791 }
792 }
793 if ( ! $settings['preselected_category'] && ! $parent_id ) {
794 OsBookingHelper::generate_bundles_folder();
795 }
796 if ( ! $settings['preselected_category'] ) {
797 echo '</div>';
798 }
799 }
800
801 public static function group_booking_btn_html( $booking_id = false ) {
802 $html = 'data-os-params="' . esc_attr(http_build_query( [ 'booking_id' => $booking_id ] )) . '"
803 data-os-action="' . esc_attr(OsRouterHelper::build_route_name( 'bookings', 'grouped_bookings_quick_view' )) . '"
804 data-os-output-target="lightbox"
805 data-os-lightbox-classes="width-500"
806 data-os-after-call="latepoint_init_grouped_bookings_form"';
807
808 return $html;
809 }
810
811 public static function quick_booking_btn_html( $booking_id = false, $params = array() ) {
812 $html = '';
813 if ( $booking_id ) {
814 $params['booking_id'] = $booking_id;
815 }
816 $route = OsRouterHelper::build_route_name( 'orders', 'quick_edit' );
817
818 $params_str = http_build_query( $params );
819 $html = 'data-os-params="' . esc_attr($params_str) . '"
820 data-os-action="' . esc_attr($route) . '"
821 data-os-output-target="side-panel"
822 data-os-after-call="latepoint_init_quick_order_form"';
823
824 return $html;
825 }
826
827
828 /**
829 * @param OsBookingModel $booking
830 *
831 * @return false|mixed
832 *
833 * Search for available location based on booking requirements. Will return false if no available location found.
834 */
835 public static function get_any_location_for_booking_by_rule( OsBookingModel $booking ) {
836 // ANY LOCATION SELECTED
837 // get available locations
838 $connected_ids = OsLocationHelper::get_location_ids_for_service_and_agent( $booking->service_id, $booking->agent_id );
839
840 // If date/time is selected - filter locations who are available at that time
841 if ( $booking->start_date && $booking->start_time ) {
842 $available_location_ids = [];
843 $booking_request = \LatePoint\Misc\BookingRequest::create_from_booking_model( $booking );
844 foreach ( $connected_ids as $location_id ) {
845 $booking_request->location_id = $location_id;
846 if ( OsBookingHelper::is_booking_request_available( $booking_request ) ) {
847 $available_location_ids[] = $location_id;
848 }
849 }
850 $connected_ids = array_intersect( $available_location_ids, $connected_ids );
851 }
852
853
854 $locations_model = new OsLocationModel();
855 if ( ! empty( $connected_ids ) ) {
856 $locations_model->where_in( 'id', $connected_ids );
857 $locations = $locations_model->should_be_active()->get_results_as_models();
858 } else {
859 $locations = [];
860 }
861
862 if ( empty( $locations ) ) {
863 return false;
864 }
865
866 $selected_location_id = $connected_ids[ wp_rand( 0, count( $connected_ids ) - 1 ) ];
867 $booking->location_id = $selected_location_id;
868
869 return $selected_location_id;
870 }
871
872 /**
873 * @param OsBookingModel $booking
874 *
875 * @return false|mixed
876 *
877 * Search for available agent based on booking requirements and agent picking preferences. Will return false if no available agent found.
878 */
879 public static function get_any_agent_for_booking_by_rule( OsBookingModel $booking ) {
880 // ANY AGENT SELECTED
881 // get available agents
882 $connected_ids = OsAgentHelper::get_agent_ids_for_service_and_location( $booking->service_id, $booking->location_id );
883
884 // If date/time is selected - filter agents who are available at that time
885 if ( $booking->start_date && $booking->start_time ) {
886 $available_agent_ids = [];
887 $booking_request = \LatePoint\Misc\BookingRequest::create_from_booking_model( $booking );
888 foreach ( $connected_ids as $agent_id ) {
889 $booking_request->agent_id = $agent_id;
890 if ( OsBookingHelper::is_booking_request_available( $booking_request ) ) {
891 $available_agent_ids[] = $agent_id;
892 }
893 }
894 $connected_ids = array_intersect( $available_agent_ids, $connected_ids );
895 }
896
897
898 /**
899 * Get IDs of agents that are eligible to be assigned a booking that has "ANY" agent pre-selected
900 *
901 * @param {array} $connected_ids Array of eligible Agent IDs
902 * @param {OsBookingModel} $booking Booking that needs agent ID
903 *
904 * @returns {array} Filtered array of IDs of eligible agents
905 * @since 4.7.6
906 * @hook latepoint_agent_ids_assignable_to_any_agent_booking
907 *
908 */
909 $connected_ids = apply_filters( 'latepoint_agent_ids_assignable_to_any_agent_booking', $connected_ids, $booking );
910
911 if ( ! empty( $connected_ids ) ) {
912 $agents_model = new OsAgentModel();
913 $agents_model->where_in( 'id', $connected_ids );
914 $agents = $agents_model->should_be_active()->get_results_as_models();
915 } else {
916 $agents = [];
917 }
918
919 if ( empty( $agents ) ) {
920 return false;
921 }
922
923
924 $selected_agent_id = false;
925 $agent_order_rule = OsSettingsHelper::get_any_agent_order();
926 switch ( $agent_order_rule ) {
927 case LATEPOINT_ANY_AGENT_ORDER_RANDOM:
928 $selected_agent_id = $connected_ids[ wp_rand( 0, count( $connected_ids ) - 1 ) ];
929 break;
930 case LATEPOINT_ANY_AGENT_ORDER_PRICE_HIGH:
931 $highest_price = false;
932 foreach ( $agents as $agent ) {
933 $booking->agent_id = $agent->id;
934 $price = OsBookingHelper::calculate_full_amount_for_booking( $booking );
935 if ( $highest_price === false && $selected_agent_id === false ) {
936 $highest_price = $price;
937 $selected_agent_id = $agent->id;
938 } else {
939 if ( $highest_price < $price ) {
940 $highest_price = $price;
941 $selected_agent_id = $agent->id;
942 }
943 }
944 }
945 break;
946 case LATEPOINT_ANY_AGENT_ORDER_PRICE_LOW:
947 $lowest_price = false;
948 foreach ( $agents as $agent ) {
949 $booking->agent_id = $agent->id;
950 $price = OsBookingHelper::calculate_full_amount_for_booking( $booking );
951 if ( $lowest_price === false && $selected_agent_id === false ) {
952 $lowest_price = $price;
953 $selected_agent_id = $agent->id;
954 } else {
955 if ( $lowest_price > $price ) {
956 $lowest_price = $price;
957 $selected_agent_id = $agent->id;
958 }
959 }
960 }
961 break;
962 case LATEPOINT_ANY_AGENT_ORDER_BUSY_HIGH:
963 $max_bookings = false;
964 foreach ( $agents as $agent ) {
965 $agent_total_bookings = OsBookingHelper::get_total_bookings_for_date( $booking->start_date, [ 'agent_id' => $agent->id ] );
966 if ( $max_bookings === false && $selected_agent_id === false ) {
967 $max_bookings = $agent_total_bookings;
968 $selected_agent_id = $agent->id;
969 } else {
970 if ( $max_bookings < $agent_total_bookings ) {
971 $max_bookings = $agent_total_bookings;
972 $selected_agent_id = $agent->id;
973 }
974 }
975 }
976 break;
977 case LATEPOINT_ANY_AGENT_ORDER_BUSY_LOW:
978 $min_bookings = false;
979 foreach ( $agents as $agent ) {
980 $agent_total_bookings = OsBookingHelper::get_total_bookings_for_date( $booking->start_date, [ 'agent_id' => $agent->id ] );
981 if ( $min_bookings === false && $selected_agent_id === false ) {
982 $min_bookings = $agent_total_bookings;
983 $selected_agent_id = $agent->id;
984 } else {
985 if ( $min_bookings > $agent_total_bookings ) {
986 $min_bookings = $agent_total_bookings;
987 $selected_agent_id = $agent->id;
988 }
989 }
990 }
991 break;
992 }
993 /**
994 * Get ID of agent that will be assigned to a booking, depending on order rules, where agent is set to ANY
995 *
996 * @param {integer} $selected_agent_id Currently selected agent ID
997 * @param {OsAgentModel[]} $agents Array of eligible agent models to pick from
998 * @param {OsBookingModel} $booking Booking that needs agent ID
999 * @param {string} $agent_order_rule Rule of agent ordering
1000 *
1001 * @returns {integer} ID of the agent that will be assigned to this booking
1002 * @since 4.7.6
1003 * @hook latepoint_get_any_agent_id_for_booking_by_rule
1004 *
1005 */
1006 $selected_agent_id = apply_filters( 'latepoint_get_any_agent_id_for_booking_by_rule', $selected_agent_id, $agents, $booking, $agent_order_rule );
1007 $booking->agent_id = $selected_agent_id;
1008
1009 return $selected_agent_id;
1010 }
1011
1012
1013 public static function get_total_bookings_for_date( $date, $conditions = [], $grouped = false ) {
1014 $args = [ 'start_date' => $date ];
1015 if ( isset( $conditions['agent_id'] ) && $conditions['agent_id'] ) {
1016 $args['agent_id'] = $conditions['agent_id'];
1017 }
1018 if ( isset( $conditions['service_id'] ) && $conditions['service_id'] ) {
1019 $args['service_id'] = $conditions['service_id'];
1020 }
1021 if ( isset( $conditions['location_id'] ) && $conditions['location_id'] ) {
1022 $args['location_id'] = $conditions['location_id'];
1023 }
1024
1025
1026 $bookings = new OsBookingModel();
1027 if ( $grouped ) {
1028 $bookings->group_by( 'start_date, start_time, end_time, service_id, location_id' );
1029 }
1030 $bookings = $bookings->where( $args );
1031
1032 return $bookings->count();
1033 }
1034
1035
1036 /**
1037 *
1038 * Get list of statuses that block timeslot availability
1039 *
1040 * @return array
1041 */
1042 public static function get_timeslot_blocking_statuses(): array {
1043 $statuses = explode( ',', OsSettingsHelper::get_settings_value( 'timeslot_blocking_statuses', '' ) );
1044
1045 /**
1046 * Get list of statuses that block timeslot availability
1047 *
1048 * @param {array} $statuses array of status codes that block timeslot availability
1049 * @returns {array} The filtered array of status codes
1050 *
1051 * @since 4.7.0
1052 * @hook latepoint_get_timeslot_blocking_statuses
1053 *
1054 */
1055 return apply_filters( 'latepoint_get_timeslot_blocking_statuses', $statuses );
1056 }
1057
1058
1059 /**
1060 *
1061 * Get list of statuses that appear on pending page
1062 *
1063 * @return array
1064 */
1065 public static function get_booking_statuses_for_pending_page(): array {
1066 $statuses = explode( ',', OsSettingsHelper::get_settings_value( 'need_action_statuses', '' ) );
1067
1068 /**
1069 * Get list of statuses that appear on pending page
1070 *
1071 * @param {array} $statuses array of status codes that appear on pending page
1072 * @returns {array} The filtered array of status codes
1073 *
1074 * @since 4.7.0
1075 * @hook latepoint_get_booking_statuses_for_pending_page
1076 *
1077 */
1078 return apply_filters( 'latepoint_get_booking_statuses_for_pending_page', $statuses );
1079 }
1080
1081 /**
1082 *
1083 * Get list of statuses that are not cancelled
1084 *
1085 * @return array
1086 */
1087 public static function get_non_cancelled_booking_statuses(): array {
1088 $statuses = self::get_statuses_list();
1089 if(isset($statuses[LATEPOINT_BOOKING_STATUS_CANCELLED])) unset($statuses[LATEPOINT_BOOKING_STATUS_CANCELLED]);
1090 $statuses = array_keys($statuses);
1091
1092 /**
1093 * Get list of statuses that are not cancelled
1094 *
1095 * @param {array} $statuses array of status codes that are not cancelled
1096 * @returns {array} The filtered array of status codes
1097 *
1098 * @since 5.0.5
1099 * @hook get_non_cancelled_booking_statuses
1100 *
1101 */
1102 return apply_filters( 'get_non_cancelled_booking_statuses', $statuses );
1103 }
1104
1105
1106 public static function get_default_booking_status( $service_id = false ) {
1107 if ( $service_id ) {
1108 $service = new OsServiceModel( $service_id );
1109 if ( $service && ! empty( $service->id ) ) {
1110 return $service->get_default_booking_status();
1111 }
1112 }
1113 $default_status = OsSettingsHelper::get_settings_value( 'default_booking_status' );
1114 if ( $default_status ) {
1115 return $default_status;
1116 } else {
1117 return LATEPOINT_BOOKING_STATUS_APPROVED;
1118 }
1119 }
1120
1121
1122 public static function change_booking_status( $booking_id, $new_status ) {
1123 $booking = new OsBookingModel( $booking_id );
1124 if ( ! $booking_id || ! $booking ) {
1125 return false;
1126 }
1127
1128 if ( $new_status == $booking->status ) {
1129 return true;
1130 } else {
1131 $old_booking = clone $booking;
1132 if ( $booking->update_status( $new_status ) ) {
1133 do_action( 'latepoint_booking_updated', $booking, $old_booking );
1134
1135 return true;
1136 } else {
1137 return false;
1138 }
1139 }
1140 }
1141
1142
1143 /**
1144 * @param \LatePoint\Misc\BookingRequest $booking_request
1145 * @param \LatePoint\Misc\BookedPeriod[]
1146 * @param int $capacity
1147 *
1148 * @return bool
1149 */
1150 public static function is_timeframe_in_booked_periods( \LatePoint\Misc\BookingRequest $booking_request, array $booked_periods, OsServiceModel $service ): bool {
1151 if ( empty( $booked_periods ) ) {
1152 return false;
1153 }
1154 $count_existing_attendees = 0;
1155 foreach ( $booked_periods as $period ) {
1156 if ( self::is_period_overlapping( $booking_request->get_start_time_with_buffer(), $booking_request->get_end_time_with_buffer(), $period->start_time_with_buffer(), $period->end_time_with_buffer() ) ) {
1157 // if it's the same service overlapping - count how many times
1158 // TODO maybe add an option to toggle on/off ability to share a timeslot capacity between two different services
1159 if ( $booking_request->service_id == $period->service_id ) {
1160 $count_existing_attendees += $period->total_attendees;
1161 } else {
1162 return true;
1163 }
1164 }
1165 }
1166 if ( $count_existing_attendees > 0 ) {
1167 // if there are attendees, check if they are below minimum need for timeslot to be blocked, if they are - then the slot is considered booked
1168 if ( ( $count_existing_attendees + $booking_request->total_attendees ) <= $service->get_capacity_needed_before_slot_is_blocked() ) {
1169 return false;
1170 } else {
1171 return true;
1172 }
1173 } else {
1174 // no attendees in the overlapping booked periods yet, just check if the requested number of attendees is within the service capacity
1175 if ( $booking_request->total_attendees <= $service->capacity_max ) {
1176 return false;
1177 } else {
1178 return true;
1179 }
1180 }
1181 }
1182
1183
1184 public static function is_period_overlapping( $period_one_start, $period_one_end, $period_two_start, $period_two_end ) {
1185 // https://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap/
1186 return ( ( $period_one_start < $period_two_end ) && ( $period_two_start < $period_one_end ) );
1187 }
1188
1189 public static function is_period_inside_another( $period_one_start, $period_one_end, $period_two_start, $period_two_end ) {
1190 return ( ( $period_one_start >= $period_two_start ) && ( $period_one_end <= $period_two_end ) );
1191 }
1192
1193 // args = [agent_id, 'service_id', 'location_id']
1194 public static function get_bookings_for_date( $date, $args = [] ) {
1195 $bookings = new OsBookingModel();
1196 $args['start_date'] = $date;
1197 // if any of these are false or 0 - remove it from arguments list
1198 if ( isset( $args['location_id'] ) && empty( $args['location_id'] ) ) {
1199 unset( $args['location_id'] );
1200 }
1201 if ( isset( $args['agent_id'] ) && empty( $args['agent_id'] ) ) {
1202 unset( $args['agent_id'] );
1203 }
1204 if ( isset( $args['service_id'] ) && empty( $args['service_id'] ) ) {
1205 unset( $args['service_id'] );
1206 }
1207
1208 $bookings->where( $args )->order_by( 'start_time asc, end_time asc, service_id asc' );
1209
1210 return $bookings->get_results_as_models();
1211 }
1212
1213 /**
1214 * @param \LatePoint\Misc\Filter $filter
1215 *
1216 * @return int
1217 */
1218 public static function count_bookings( \LatePoint\Misc\Filter $filter ) {
1219 $bookings = new OsBookingModel();
1220 $query_args = [];
1221 if ( $filter->date_from ) {
1222 $query_args['start_date'] = $filter->date_from;
1223 }
1224 if ( $filter->location_id ) {
1225 $query_args['location_id'] = $filter->location_id;
1226 }
1227 if ( $filter->agent_id ) {
1228 $query_args['agent_id'] = $filter->agent_id;
1229 }
1230 if ( $filter->service_id ) {
1231 $query_args['service_id'] = $filter->service_id;
1232 }
1233
1234 return $bookings->should_not_be_cancelled()->where( $query_args )->count();
1235 }
1236
1237
1238 public static function get_nice_status_name( $status ) {
1239 $statuses_list = OsBookingHelper::get_statuses_list();
1240 if ( $status && isset( $statuses_list[ $status ] ) ) {
1241 return $statuses_list[ $status ];
1242 } else {
1243 return __( 'Undefined Status', 'latepoint' );
1244 }
1245 }
1246
1247
1248 public static function get_statuses_list() {
1249 $statuses = [
1250 LATEPOINT_BOOKING_STATUS_APPROVED => __( 'Approved', 'latepoint' ),
1251 LATEPOINT_BOOKING_STATUS_PENDING => __( 'Pending Approval', 'latepoint' ),
1252 LATEPOINT_BOOKING_STATUS_CANCELLED => __( 'Cancelled', 'latepoint' ),
1253 LATEPOINT_BOOKING_STATUS_NO_SHOW => __( 'No Show', 'latepoint' ),
1254 LATEPOINT_BOOKING_STATUS_COMPLETED => __( 'Completed', 'latepoint' ),
1255 ];
1256 $additional_statuses = array_map( 'trim', explode( ',', OsSettingsHelper::get_settings_value( 'additional_booking_statuses', '' ) ) );
1257 if ( ! empty( $additional_statuses ) ) {
1258 foreach ( $additional_statuses as $status ) {
1259 if ( ! empty( $status ) ) {
1260 $statuses[ str_replace( ' ', '_', strtolower( $status ) ) ] = $status;
1261 }
1262 }
1263 }
1264 $statuses = apply_filters( 'latepoint_booking_statuses', $statuses );
1265
1266 return $statuses;
1267 }
1268
1269
1270 public static function get_weekdays_arr( $full_name = false ) {
1271 if ( $full_name ) {
1272 $weekdays = array(
1273 __( 'Monday', 'latepoint' ),
1274 __( 'Tuesday', 'latepoint' ),
1275 __( 'Wednesday', 'latepoint' ),
1276 __( 'Thursday', 'latepoint' ),
1277 __( 'Friday', 'latepoint' ),
1278 __( 'Saturday', 'latepoint' ),
1279 __( 'Sunday', 'latepoint' )
1280 );
1281 } else {
1282 $weekdays = array(
1283 __( 'Mon', 'latepoint' ),
1284 __( 'Tue', 'latepoint' ),
1285 __( 'Wed', 'latepoint' ),
1286 __( 'Thu', 'latepoint' ),
1287 __( 'Fri', 'latepoint' ),
1288 __( 'Sat', 'latepoint' ),
1289 __( 'Sun', 'latepoint' )
1290 );
1291 }
1292
1293 return $weekdays;
1294 }
1295
1296 public static function get_weekday_name_by_number( $weekday_number, $full_name = false ) {
1297 $weekdays = OsBookingHelper::get_weekdays_arr( $full_name );
1298 if ( ! isset( $weekday_number ) || $weekday_number < 1 || $weekday_number > 7 ) {
1299 return '';
1300 } else {
1301 return $weekdays[ $weekday_number - 1 ];
1302 }
1303 }
1304
1305 public static function get_stat( $stat, $args = [] ) {
1306 if ( ! in_array( $stat, [ 'duration', 'price', 'bookings' ] ) ) {
1307 return false;
1308 }
1309 $defaults = [
1310 'customer_id' => false,
1311 'agent_id' => false,
1312 'service_id' => false,
1313 'location_id' => false,
1314 'date_from' => false,
1315 'date_to' => false,
1316 'group_by' => false,
1317 'exclude_status' => false
1318 ];
1319 $args = array_merge( $defaults, $args );
1320 $bookings = new OsBookingModel();
1321 $query_args = array( $args['date_from'], $args['date_to'] );
1322 switch ( $stat ) {
1323 case 'duration':
1324 $stat_query = 'SUM(end_time - start_time)';
1325 break;
1326 case 'price':
1327 $stat_query = 'sum(total)';
1328 break;
1329 case 'bookings':
1330 $stat_query = 'count(id)';
1331 break;
1332 }
1333 $select_query = $stat_query . ' as stat';
1334 if ( $args['group_by'] ) {
1335 $select_query .= ',' . $args['group_by'];
1336 }
1337 $bookings->select( $select_query );
1338
1339
1340 if ( $args['date_from'] ) {
1341 $bookings->where( [ 'start_date >=' => $args['date_from'] ] );
1342 }
1343 if ( $args['date_to'] ) {
1344 $bookings->where( [ 'start_date <=' => $args['date_to'] ] );
1345 }
1346 if ( $args['service_id'] ) {
1347 $bookings->where( [ 'service_id' => $args['service_id'] ] );
1348 }
1349 if ( $args['agent_id'] ) {
1350 $bookings->where( [ 'agent_id' => $args['agent_id'] ] );
1351 }
1352 if ( $args['location_id'] ) {
1353 $bookings->where( [ 'location_id' => $args['location_id'] ] );
1354 }
1355 if ( $args['customer_id'] ) {
1356 $bookings->where( [ 'customer_id' => $args['customer_id'] ] );
1357 }
1358 if ( $args['group_by'] ) {
1359 $bookings->group_by( $args['group_by'] );
1360 }
1361 // TODO, need to support custom status exclusions
1362 if ( $args['exclude_status'] == LATEPOINT_BOOKING_STATUS_CANCELLED ) {
1363 $bookings->should_not_be_cancelled();
1364 }
1365
1366 $stat_total = $bookings->get_results( ARRAY_A );
1367 if ( $args['group_by'] ) {
1368 return $stat_total;
1369 } else {
1370 return isset( $stat_total[0]['stat'] ) ? $stat_total[0]['stat'] : 0;
1371 }
1372 }
1373
1374 public static function get_new_customer_stat_for_period( DateTime $date_from, DateTime $date_to, \LatePoint\Misc\Filter $filter ) {
1375 // TODO make sure filter is respected
1376 $customers = new OsCustomerModel();
1377
1378 return $customers->filter_allowed_records()->where( [
1379 'created_at >=' => $date_from->format( 'Y-m-d' ),
1380 'created_at <=' => $date_to->format( 'Y-m-d' )
1381 ] )->count();
1382 }
1383
1384 public static function get_stat_for_period( $stat, $date_from, $date_to, \LatePoint\Misc\Filter $filter, $group_by = false ) {
1385 if ( ! in_array( $stat, [ 'duration', 'price', 'bookings' ] ) ) {
1386 return false;
1387 }
1388 if ( ! in_array( $group_by, [ false, 'agent_id', 'service_id', 'location_id' ] ) ) {
1389 return false;
1390 }
1391 $bookings = new OsBookingModel();
1392 switch ( $stat ) {
1393 case 'duration':
1394 $stat_query = 'SUM(end_time - start_time)';
1395 break;
1396 case 'price':
1397 $stat_query = 'sum(' . LATEPOINT_TABLE_ORDER_ITEMS . '.subtotal)';
1398 $bookings->join( LATEPOINT_TABLE_ORDER_ITEMS, [ 'id' => $bookings->table_name . '.order_item_id' ] );
1399 $bookings->join( LATEPOINT_TABLE_ORDERS, [ 'id' => LATEPOINT_TABLE_ORDER_ITEMS . '.order_id' ] );
1400 break;
1401 case 'bookings':
1402 $stat_query = 'count(id)';
1403 break;
1404 }
1405 $select_query = $stat_query . ' as stat';
1406 if ( $group_by ) {
1407 $select_query .= ',' . $group_by;
1408 }
1409 $bookings->select( $select_query )->where( [ 'start_date >=' => $date_from, 'start_date <= ' => $date_to ] );
1410
1411 if ( $filter->service_id ) {
1412 $bookings->where( [ 'service_id' => $filter->service_id ] );
1413 }
1414 if ( $filter->agent_id ) {
1415 $bookings->where( [ 'agent_id' => $filter->agent_id ] );
1416 }
1417 if ( $filter->location_id ) {
1418 $bookings->where( [ 'location_id' => $filter->location_id ] );
1419 }
1420
1421 $bookings->should_not_be_cancelled();
1422
1423 if ( $group_by ) {
1424 $bookings->group_by( $group_by );
1425 }
1426
1427 $stat_total = $bookings->get_results( ARRAY_A );
1428 if ( $group_by ) {
1429 return $stat_total;
1430 } else {
1431 return isset( $stat_total[0]['stat'] ) ? $stat_total[0]['stat'] : 0;
1432 }
1433 }
1434
1435 public static function get_total_bookings_per_day_for_period( $date_from, $date_to, \LatePoint\Misc\Filter $filter ) {
1436 $bookings = new OsBookingModel();
1437 $bookings->select( 'count(id) as bookings_per_day, start_date' )
1438 ->where( [ 'start_date >=' => $date_from, 'start_date <=' => $date_to ] )
1439 ->where( [ 'status NOT IN' => OsCalendarHelper::get_booking_statuses_hidden_from_calendar() ] );
1440 if ( $filter->service_id ) {
1441 $bookings->where( [ 'service_id' => $filter->service_id ] );
1442 }
1443 if ( $filter->agent_id ) {
1444 $bookings->where( [ 'agent_id' => $filter->agent_id ] );
1445 }
1446 if ( $filter->location_id ) {
1447 $bookings->where( [ 'location_id' => $filter->location_id ] );
1448 }
1449 $bookings->group_by( 'start_date' );
1450
1451 return $bookings->get_results();
1452 }
1453
1454
1455 public static function get_min_max_work_periods( $specific_weekdays = false, $service_id = false, $agent_id = false ) {
1456 $select_string = 'MIN(start_time) as start_time, MAX(end_time) as end_time';
1457 $work_periods = new OsWorkPeriodModel();
1458 $work_periods = $work_periods->select( $select_string );
1459 $query_args = array( 'service_id' => 0, 'agent_id' => 0 );
1460 if ( $service_id ) {
1461 $query_args['service_id'] = $service_id;
1462 }
1463 if ( $agent_id ) {
1464 $query_args['agent_id'] = $agent_id;
1465 }
1466 if ( $specific_weekdays && ! empty( $specific_weekdays ) ) {
1467 $query_args['week_day'] = $specific_weekdays;
1468 }
1469 $results = $work_periods->set_limit( 1 )->where( $query_args )->get_results( ARRAY_A );
1470 if ( ( $service_id || $agent_id ) && empty( $results['min_start_time'] ) ) {
1471 if ( $service_id && empty( $results['min_start_time'] ) ) {
1472 $query_args['service_id'] = 0;
1473 $work_periods = new OsWorkPeriodModel();
1474 $work_periods = $work_periods->select( $select_string );
1475 $results = $work_periods->set_limit( 1 )->where( $query_args )->get_results( ARRAY_A );
1476 }
1477 if ( $agent_id && empty( $results['min_start_time'] ) ) {
1478 $query_args['agent_id'] = 0;
1479 $work_periods = new OsWorkPeriodModel();
1480 $work_periods = $work_periods->select( $select_string );
1481 $results = $work_periods->set_limit( 1 )->where( $query_args )->get_results( ARRAY_A );
1482 }
1483 }
1484 if ( $results ) {
1485 return array( $results['start_time'], $results['end_time'] );
1486 } else {
1487 return false;
1488 }
1489 }
1490
1491
1492 public static function get_work_start_end_time_for_multiple_dates( $dates = false, $service_id = false, $agent_id = false ) {
1493 $specific_weekdays = array();
1494 if ( $dates ) {
1495 foreach ( $dates as $date ) {
1496 $target_date = new OsWpDateTime( $date );
1497 $weekday = $target_date->format( 'N' );
1498 if ( ! in_array( $weekday, $specific_weekdays ) ) {
1499 $specific_weekdays[] = $weekday;
1500 }
1501 }
1502 }
1503 $work_minmax_start_end = self::get_min_max_work_periods( $specific_weekdays, $service_id, $agent_id );
1504
1505 return $work_minmax_start_end;
1506 }
1507
1508 /**
1509 * @param int $minute
1510 * @param \LatePoint\Misc\WorkPeriod[] $work_periods_arr
1511 *
1512 * @return bool
1513 */
1514 public static function is_minute_in_work_periods( int $minute, array $work_periods_arr ): bool {
1515 // print_r($work_periods_arr);
1516 if ( empty( $work_periods_arr ) ) {
1517 return false;
1518 }
1519 foreach ( $work_periods_arr as $work_period ) {
1520 // end of period does not count because we cant make appointment with 0 duration
1521 if ( $work_period->start_time <= $minute && $work_period->end_time > $minute ) {
1522 return true;
1523 }
1524 }
1525
1526 return false;
1527 }
1528
1529 public static function get_calendar_start_end_time( $bookings, $work_start_minutes, $work_end_minutes ) {
1530 $calendar_start_minutes = $work_start_minutes;
1531 $calendar_end_minutes = $work_end_minutes;
1532 if ( $bookings ) {
1533 foreach ( $bookings as $bookings_for_agent ) {
1534 if ( $bookings_for_agent ) {
1535 foreach ( $bookings_for_agent as $booking ) {
1536 if ( $booking->start_time < $calendar_start_minutes ) {
1537 $calendar_start_minutes = $booking->start_time;
1538 }
1539 if ( $booking->end_time > $calendar_end_minutes ) {
1540 $calendar_end_minutes = $booking->end_time;
1541 }
1542 }
1543 }
1544 }
1545 }
1546
1547 return [ $calendar_start_minutes, $calendar_end_minutes ];
1548 }
1549
1550 public static function generate_direct_manage_booking_url( OsBookingModel $booking, string $for ): string {
1551 if ( ! in_array( $for, [ 'agent', 'customer' ] ) ) {
1552 return '';
1553 }
1554 $key = $booking->get_key_to_manage_for($for);
1555 $url = OsRouterHelper::build_admin_post_link( [ 'manage_booking_by_key', 'show' ], [ 'key' => $key ] );
1556
1557 return $url;
1558 }
1559
1560 public static function generate_summary_actions_for_booking(OsBookingModel $booking, ?string $key = null){
1561 ?>
1562 <div class="booking-full-summary-actions">
1563 <div class="add-to-calendar-wrapper">
1564 <a href="#" class="open-calendar-types booking-summary-action-btn"><i class="latepoint-icon latepoint-icon-calendar"></i><span><?php esc_html_e('Add to Calendar', 'latepoint'); ?></span></a>
1565 <?php echo OsBookingHelper::generate_add_to_calendar_links($booking, $key ?? $booking->get_key_to_manage_for('customer')); ?>
1566 </div>
1567 <a href="<?php echo esc_url($booking->get_print_link($key ?? $booking->get_key_to_manage_for('customer'))); ?>" class="print-booking-btn booking-summary-action-btn" target="_blank"><i class="latepoint-icon latepoint-icon-printer"></i><span><?php esc_html_e('Print', 'latepoint'); ?></span></a>
1568 <?php
1569 if($booking->is_upcoming()){
1570 if(OsCustomerHelper::can_reschedule_booking($booking)){ ?>
1571 <a href="#" class="latepoint-request-booking-reschedule booking-summary-action-btn" data-os-after-call="latepoint_init_reschedule" data-os-lightbox-classes="width-450 reschedule-calendar-wrapper" data-os-action="<?php echo esc_attr(OsRouterHelper::build_route_name('manage_booking_by_key', 'request_reschedule_calendar')); ?>" data-os-params="<?php echo esc_attr(OsUtilHelper::build_os_params(['key' => $key ?? $booking->get_key_to_manage_for('customer')])); ?>" data-os-output-target="lightbox">
1572 <i class="latepoint-icon latepoint-icon-calendar"></i>
1573 <span><?php esc_html_e('Reschedule', 'latepoint'); ?></span>
1574 </a>
1575 <?php
1576 }
1577 if(OsCustomerHelper::can_cancel_booking($booking)){ ?>
1578 <a href="#" class="booking-summary-action-btn cancel-appointment-btn"
1579 data-os-prompt="<?php esc_attr_e('Are you sure you want to cancel this appointment?', 'latepoint'); ?>"
1580 data-os-success-action="reload"
1581 data-os-action="<?php echo esc_attr(OsRouterHelper::build_route_name('manage_booking_by_key', 'request_cancellation')); ?>"
1582 data-os-params="<?php echo esc_attr(OsUtilHelper::build_os_params(['key' => $key ?? $booking->get_key_to_manage_for('customer')])); ?>">
1583 <i class="latepoint-icon latepoint-icon-ui-24"></i>
1584 <span><?php esc_html_e('Cancel', 'latepoint'); ?></span>
1585 </a>
1586 <?php
1587 }
1588 }
1589 do_action('latepoint_booking_summary_after_booking_actions', $booking);
1590 ?>
1591 </div>
1592 <?php
1593 }
1594
1595 public static function generate_summary_for_booking( OsBookingModel $booking, $cart_item_id = false, ?string $viewer = 'customer' ): string {
1596 $summary_html = '';
1597 $summary_html.= apply_filters( 'latepoint_booking_summary_before_summary_box', '', $booking );
1598 $summary_html.= '<div class="summary-box main-box" ' . ( ( $cart_item_id ) ? 'data-cart-item-id="' . $cart_item_id . '"' : '' ) . '>';
1599 $output_timezone_name = $viewer == 'customer' ? $booking->get_customer_timezone_name() : OsTimeHelper::get_wp_timezone_name();
1600 if(!empty($booking->start_datetime_utc)) {
1601 $summary_html .= '<div class="summary-box-booking-date-box">';
1602 $summary_html .= '<div class="summary-box-booking-date-day">' . $booking->start_datetime_in_format( 'j', $output_timezone_name ) . '</div>';
1603 $summary_html .= '<div class="summary-box-booking-date-month">' . OsUtilHelper::get_month_name_by_number( $booking->start_datetime_in_format( 'n', $output_timezone_name ), true ) . '</div>';
1604 $summary_html .= '</div>';
1605 }
1606 $summary_html.= '<div class="summary-box-inner">';
1607 $service_headings = [];
1608 $service_headings = apply_filters( 'latepoint_booking_summary_service_headings', $service_headings, $booking );
1609 if ( $service_headings ) {
1610 $summary_html .= '<div class="summary-box-heading">';
1611 foreach ( $service_headings as $heading ) {
1612 $summary_html .= '<div class="sbh-item">' . $heading . '</div>';
1613 }
1614 $summary_html .= '<div class="sbh-line"></div>';
1615 $summary_html .= '</div>';
1616 }
1617 $summary_html .= '<div class="summary-box-content os-cart-item">';
1618 if ( $cart_item_id && OsCartsHelper::can_checkout_multiple_items() ) {
1619 $summary_html .= '<div class="os-remove-item-from-cart" role="button" tabindex="0" data-confirm-text="' . __( 'Are you sure you want to remove this item from your cart?', 'latepoint' ) . '" data-cart-item-id="' . $cart_item_id . '" data-route="' . OsRouterHelper::build_route_name( 'carts', 'remove_item_from_cart' ) . '">
1620 <div class="os-remove-from-cart-icon"></div>
1621 </div>';
1622 }
1623 $summary_html .= '<div class="sbc-big-item">' . $booking->get_service_name_for_summary() . '</div>';
1624 if ( $booking->start_date ) {
1625 $summary_html .= '<div class="sbc-highlighted-item">' . $booking->get_nice_datetime_for_summary($viewer) . '</div>';
1626 }
1627 /**
1628 * Output summary of the booking data after a start date and time
1629 *
1630 * @since 5.2.0
1631 * @hook latepoint_summary_booking_info_after_start_date
1632 *
1633 * @param {string} $summary_html HTML of the summary
1634 * @param {OsBookingModel} $booking Booking object that is being outputted
1635 * @param {string} $cart_item_id ID of a cart item this booking belongs to
1636 * @param {string} $viewer determines who is viewing this summary, can be customer or agent
1637 *
1638 * @returns {string} Filtered HTML
1639 */
1640 $summary_html = apply_filters('latepoint_summary_booking_info_after_start_date', $summary_html, $booking, $cart_item_id, $viewer);
1641 $summary_html .= '</div>';
1642
1643 $service_attributes = [];
1644 $service_attributes = apply_filters( 'latepoint_booking_summary_service_attributes', $service_attributes, $booking );
1645 if ( $service_attributes ) {
1646 $summary_html .= '<div class="summary-attributes sa-clean">';
1647 foreach ( $service_attributes as $attribute ) {
1648 $summary_html .= '<span>' . $attribute['label'] . ': <strong>' . $attribute['value'] . '</strong></span>';
1649 }
1650 $summary_html .= '</div>';
1651 }
1652 $summary_html .= '</div>';
1653 $summary_html .= apply_filters( 'latepoint_booking_summary_after_summary_box_inner', '', $booking );
1654 $summary_html .= '</div>';
1655 $summary_html.= apply_filters( 'latepoint_booking_summary_after_summary_box', '', $booking );
1656
1657 return $summary_html;
1658 }
1659
1660
1661 /**
1662 * @param OsBookingModel[] $bookings
1663 *
1664 * @return bool
1665 */
1666 public static function bookings_have_same_agent( array $bookings ): bool {
1667 return ( count( array_unique( array_column( $bookings, 'agent_id' ) ) ) == 1 );
1668 }
1669
1670 /**
1671 * @param OsBookingModel[] $bookings
1672 *
1673 * @return bool
1674 */
1675 public static function bookings_have_same_location( array $bookings ): bool {
1676 return ( count( array_unique( array_column( $bookings, 'location_id' ) ) ) == 1 );
1677 }
1678
1679 /**
1680 * @param OsBookingModel[] $bookings
1681 *
1682 * @return bool
1683 */
1684 public static function bookings_have_same_service( array $bookings ): bool {
1685 return ( count( array_unique( array_column( $bookings, 'service_id' ) ) ) == 1 );
1686 }
1687
1688 public static function prepare_new_from_params( array $params ): OsBookingModel {
1689 $booking = new OsBookingModel();
1690
1691 $services = OsServiceHelper::get_allowed_active_services();
1692 $agents = OsAgentHelper::get_allowed_active_agents();
1693
1694 // LOAD FROM PASSED PARAMS
1695 $booking->order_item_id = $params['order_item_id'] ?? '';
1696 $booking->service_id = ! empty( $params['service_id'] ) ? OsUtilHelper::first_value_if_array( $params['service_id'] ) : '';
1697 if ( empty( $booking->service_id ) && ! empty( $services ) ) {
1698 $booking->service_id = $services[0]->id;
1699 }
1700
1701 $booking->agent_id = ! empty( $params['agent_id'] ) ? OsUtilHelper::first_value_if_array( $params['agent_id'] ) : '';
1702 if ( empty( $booking->agent_id ) && ! empty( $agents ) ) {
1703 $booking->agent_id = $agents[0]->id;
1704 }
1705
1706 if ( ! empty( $params['order_id'] ) ) {
1707 $order = new OsOrderModel( $params['order_id'] );
1708 $booking->customer_id = $order->customer_id;
1709 } else {
1710 $booking->customer_id = ! empty( $params['customer_id'] ) ? OsUtilHelper::first_value_if_array( $params['customer_id'] ) : '';
1711 }
1712
1713 $booking->location_id = ! empty( $params['location_id'] ) ? OsUtilHelper::first_value_if_array( $params['location_id'] ) : OsLocationHelper::get_default_location_id( true );
1714 $booking->start_date = $params['start_date'] ?? OsTimeHelper::today_date( 'Y-m-d' );
1715 $booking->start_time = $params['start_time'] ?? 600;
1716
1717 $booking->end_time = ( $booking->service_id ) ? $booking->calculate_end_time() : $booking->start_time + 60;
1718 $booking->end_date = $booking->calculate_end_date();
1719 $booking->buffer_before = ( $booking->service_id ) ? $booking->service->buffer_before : 0;
1720 $booking->buffer_after = ( $booking->service_id ) ? $booking->service->buffer_after : 0;
1721 $booking->status = LATEPOINT_BOOKING_STATUS_APPROVED;
1722
1723 return $booking;
1724 }
1725
1726
1727 }
1728