[Back] <?php
/*
* Copyright (c) 2022 LatePoint LLC. All rights reserved.
*/
class OsResourceHelper {
/**
* @param \LatePoint\Misc\BookingRequest $booking_request
* @param DateTime $date_from
* @param DateTime|null $date_to
* @param array $settings
*
* @return array
*
*
* Returns an array of work periods, grouped by days that were requested in the filter.
* example: ['2022-02-24' => [], '2022-02-25' => [], ...]
*
* | Agent | Service | Location | Date | Hours | Weight
* | -----------------------------------------------------------------------------
* | 1 | 1 | 1 | 2022-01-15 | 7:00 - 18:00 | 7
* | 1 | 0 | 1 | 2022-01-15 | 8:00 - 14:00 | 6
* | 1 | 0 | 0 | 2022-01-15 | 8:00 - 14:00 | 5
* | 0 | 0 | 1 | 2022-01-15 | 11:00 - 12:00 | 5
* | 0 | 0 | 0 | 2022-01-15 | 11:00 - 12:00 | 4
* | 1 | 0 | 1 | NULL | 0:00 - 0:00 | 2
* | 1 | 0 | 0 | NULL | 9:00 - 12:00 | 1
* | 0 | 0 | 0 | NULL | 11:00 - 17:00 | 0
*
*/
public static function get_resources_grouped_by_day( \LatePoint\Misc\BookingRequest $booking_request, DateTime $date_from, ?DateTime $date_to = null, array $settings = [] ): array {
$defaults = [
'now' => OsTimeHelper::now_datetime_object(),
'exclude_booking_ids' => [],
'accessed_from_backend' => false,
'consider_cart_items' => false,
'timezone_name' => OsTimeHelper::get_wp_timezone_name()
];
$settings = array_merge( $defaults, $settings );
$connections = OsConnectorHelper::get_connections_that_satisfy_booking_request( $booking_request, $settings['accessed_from_backend'] );
$resources = [];
foreach ( $connections as $connection ) {
$resources[] = \LatePoint\Misc\BookingResource::create_from_connection( $connection );
}
if ( empty( $date_to ) ) {
$date_to = clone $date_from;
}
// all resource management is done in WP timezone, if requested timezone is different - make sure to include couple days on each end to accommodate for timezone differences, which could be up to 26 hours
if($settings['timezone_name'] != OsTimeHelper::get_wp_timezone_name()) {
$date_from->modify( '-2 days' );
$date_to->modify( '+2 days' );
}
$filter = new \LatePoint\Misc\Filter( [
'service_id' => $booking_request->service_id,
'connections' => $connections,
'date_from' => $date_from->format( 'Y-m-d' ),
'date_to' => $date_to->format( 'Y-m-d' )
] );
$weekday_periods = OsWorkPeriodsHelper::get_work_periods_grouped_by_weekday( $filter );
$daily_resources = [];
// loop through the requested days and fill in array with work periods that are applicable to that day
// Booked periods
$booked_periods_filter = new \LatePoint\Misc\Filter();
$booked_periods_filter->date_from = $date_from->format( 'Y-m-d' );
$booked_periods_filter->date_to = $date_to->format( 'Y-m-d' );
$booked_periods_filter->statuses = OsBookingHelper::get_timeslot_blocking_statuses();
if ( $settings['exclude_booking_ids'] ) {
$booked_periods_filter->exclude_booking_ids = $settings['exclude_booking_ids'];
}
if ( $settings['consider_cart_items'] ) {
$booked_periods_filter->consider_cart_items = true;
}
$booked_periods = OsBookingHelper::get_booked_periods_grouped_by_day( $booked_periods_filter );
$blocked_periods = OsBookingHelper::get_blocked_periods_grouped_by_day( $filter, $settings['accessed_from_backend'] );
for ( $day_date = clone $date_from; $day_date->format( 'Y-m-d' ) <= $date_to->format( 'Y-m-d' ); $day_date->modify( '+1 day' ) ) {
$daily_resources[ $day_date->format( 'Y-m-d' ) ] = [];
// fill every day with available resources
foreach ( $resources as $resource ) {
$last_added_period = false;
$available_work_periods_groups = [];
$group_index = 0;
// loop through available work periods for this week day
foreach ( $weekday_periods[ $day_date->format( 'N' ) ] as $period ) {
if ( $period->custom_date && ( $period->custom_date != $day_date->format( 'Y-m-d' ) ) ) {
continue;
} // if this period has a custom date set and if it doesn't match the one we search for - skip it
// only add this work period if agent/location/service match or not set
if ( ( ! $period->agent_id || $period->agent_id == $resource->agent_id ) && ( ! $period->service_id || $period->service_id == $resource->service_id ) && ( ! $period->location_id || $period->location_id == $resource->location_id ) ) {
if ( $last_added_period ) {
// if weight of previously added period is different - break, no need to add any other periods
if ( $last_added_period->weight != $period->weight ) {
break;
}
if ( ( $last_added_period->service_id != $period->service_id ) || ( $last_added_period->agent_id != $period->agent_id ) || ( $last_added_period->location_id != $period->location_id ) ) {
// same weight NOT same exact properties, create a new group of work periods, which will later be used to find intersections
$group_index ++;
}
if ( ( $period->start_time == 0 ) && ( $period->end_time == 0 ) ) {
$available_work_periods_groups = [];
break;
}
$available_work_periods_groups[ $group_index ][] = \LatePoint\Misc\TimePeriod::create_from_work_period( $period );
$last_added_period = $period;
} else {
if ( ( $period->start_time == 0 ) && ( $period->end_time == 0 ) ) {
$available_work_periods_groups = [];
break;
}
$available_work_periods_groups[ $group_index ][] = \LatePoint\Misc\TimePeriod::create_from_work_period( $period );
$last_added_period = $period;
}
}
}
$day_resource = clone $resource;
$day_resource->date = $day_date->format( 'Y-m-d' );
$day_resource->add_available_periods( $available_work_periods_groups );
/// -----------------------------
/// LOGIC FOR CALCULATING "BOOKED" PERIODS
/// -----------------------------
foreach ( $booked_periods[ $day_date->format( 'Y-m-d' ) ] as $booked_period ) {
if ( ( $day_resource->service_id == $booked_period->service_id ) && ( $day_resource->location_id == $booked_period->location_id ) && ( $day_resource->agent_id == $booked_period->agent_id ) ) {
// same service, agent and location is already booked, block no matter what
$day_resource->add_booked_period( $booked_period );
continue;
}
if ( $day_resource->agent_id == $booked_period->agent_id ) {
// Same agent
if ( ( $day_resource->location_id != $booked_period->location_id ) && OsSettingsHelper::is_on( 'one_location_at_time' ) ) {
// different location is booked, but the same agent, block if "Agents can only be present in one location at a time" is ON
$day_resource->add_booked_period( $booked_period, true );
continue;
}
if ( ( $day_resource->service_id != $booked_period->service_id ) && ( $day_resource->location_id == $booked_period->location_id ) && ! OsSettingsHelper::is_on( 'multiple_services_at_time' ) ) {
// Different service, but same location, block, if "One agent can perform different services simultaneously" is OFF
$day_resource->add_booked_period( $booked_period, true );
}
} else {
// Different agent
if ( ( $day_resource->location_id == $booked_period->location_id ) && OsSettingsHelper::is_on( 'one_agent_at_location' ) ) {
// same location, so it doesn't matter who's the agent, block, because location can only be used by a single agent at a time
// set to max capacity, to block slot even if it still has room
$day_resource->add_booked_period( $booked_period, true );
}
}
}
/// -----------------------------
/// LOGIC FOR CALCULATING "BLOCKED" PERIODS
/// -----------------------------
foreach ( $blocked_periods[ $day_date->format( 'Y-m-d' ) ] as $blocked_period ) {
if ( ! $blocked_period->agent_id || $day_resource->agent_id == $blocked_period->agent_id ) {
$day_resource->add_blocked_period( $blocked_period );
}
}
$day_resource->build_bookable_slots( $booking_request, $day_resource->get_timeblock_interval() );
$daily_resources[ $day_date->format( 'Y-m-d' ) ][] = $day_resource;
}
}
$daily_resources = apply_filters( 'latepoint_get_resources_grouped_by_day', $daily_resources, $booking_request, $date_from, $date_to, $settings );
return $daily_resources;
}
/**
* @param \LatePoint\Misc\BookingResource[] $resources
*
* @return array
*/
public static function get_ordered_booking_slots_from_resources( array $resources ): array {
$booking_slots = [];
foreach ( $resources as $resource ) {
$booking_slots = array_merge( $booking_slots, $resource->slots );
}
usort( $booking_slots, function ( $first, $second ) {
return $first->start_time <=> $second->start_time;
} );
if ( count( $resources ) > 1 ) {
$squashed_booking_slots = [];
$last_added_slot = false;
foreach ( $booking_slots as $booking_slot ) {
if ( $last_added_slot && ( $last_added_slot->start_time == $booking_slot->start_time ) ) {
if ( $last_added_slot->available_capacity() < $booking_slot->available_capacity() ) {
$squashed_booking_slots[ count( $squashed_booking_slots ) - 1 ] = $booking_slot;
$last_added_slot = $booking_slot;
}
} else {
$squashed_booking_slots[] = $booking_slot;
$last_added_slot = $booking_slot;
}
}
$booking_slots = $squashed_booking_slots;
}
return $booking_slots;
}
/**
* @param \LatePoint\Misc\BookingResource[]
*
* @return \LatePoint\Misc\TimePeriod
*/
public static function get_work_boundaries_for_resources( $resources ): \LatePoint\Misc\TimePeriod {
$times = [];
foreach ( $resources as $resource ) {
foreach ( $resource->work_time_periods as $work_time_period ) {
$times[] = $work_time_period->start_time;
$times[] = $work_time_period->end_time;
}
foreach ( $resource->booked_time_periods as $booked_time_period ) {
if ( $booked_time_period->start_date == $booked_time_period->end_date ) {
// same day event
$times[] = $booked_time_period->start_time;
$times[] = $booked_time_period->end_time;
} else {
// event spans mutiple days, expand boundaries to a full day
$times[] = 0;
$times[] = 24 * 60 - 1;
}
}
}
if ( $times ) {
$boundary_time_period = new \LatePoint\Misc\TimePeriod( [
'start_time' => min( $times ),
'end_time' => max( $times )
] );
} else {
$boundary_time_period = new \LatePoint\Misc\TimePeriod( [ 'start_time' => 0, 'end_time' => 0 ] );
}
return $boundary_time_period;
}
/**
* @param array $groups_of_resources
*
* @return \LatePoint\Misc\TimePeriod
*/
public static function get_work_boundaries_for_groups_of_resources( array $groups_of_resources ): \LatePoint\Misc\TimePeriod {
$times = [];
foreach ( $groups_of_resources as $resources ) {
$time_period = self::get_work_boundaries_for_resources( $resources );
if ( $time_period->start_time || $time_period->end_time ) {
$times[] = $time_period->start_time;
$times[] = $time_period->end_time;
}
}
if ( $times ) {
$boundary_time_period = new \LatePoint\Misc\TimePeriod( [
'start_time' => min( $times ),
'end_time' => max( $times )
] );
} else {
$boundary_time_period = new \LatePoint\Misc\TimePeriod( [ 'start_time' => 0, 'end_time' => 0 ] );
}
return $boundary_time_period;
}
}