<?php /* * Copyright (c) 2022 LatePoint LLC. All rights reserved. */ namespace LatePoint\Misc; class BookingResource{ public int $agent_id; public int $service_id; public int $location_id; public int $max_capacity; public int $min_capacity; public int $min_capacity_to_be_blocked; public int $timeblock_interval; public string $date; public array $work_time_periods = []; public array $booked_time_periods = []; // time periods that are usually part of work periods but a blocked now for some reason(time past today), // it's different from booked periods, because they have no bookings public array $blocked_time_periods = []; public array $slots = []; public array $bookable_minutes = []; public array $work_minutes = []; function __construct($args = []){ $allowed_props = self::allowed_props(); foreach($args as $key => $arg){ if(in_array($key, $allowed_props)) $this->$key = $arg; } } /** * @return \OsAgentModel */ function get_agent(): \OsAgentModel{ return ($this->agent_id) ? new \OsAgentModel($this->agent_id) : new \OsAgentModel(); } protected function retrieve_service_data(){ $service = new \OsServiceModel($this->service_id); $this->max_capacity = $service->capacity_max; $this->timeblock_interval = $service->timeblock_interval ? intval($service->timeblock_interval) : \OsSettingsHelper::get_default_timeblock_interval(); $this->min_capacity_to_be_blocked = $service->get_capacity_needed_before_slot_is_blocked(); } public function get_timeblock_interval(){ if(isset($this->timeblock_interval)){ return $this->timeblock_interval; }else{ $this->retrieve_service_data(); return $this->timeblock_interval; } } public function get_min_capacity_to_be_blocked(){ if(isset($this->min_capacity_to_be_blocked)){ return $this->min_capacity_to_be_blocked; }else{ $this->retrieve_service_data(); return $this->min_capacity_to_be_blocked; } } public function get_max_capacity(){ if(isset($this->max_capacity)){ return $this->max_capacity; }else{ $this->retrieve_service_data(); return $this->max_capacity; } } /** * @param BookingRequest $booking_request * @param int $selectable_time_interval * @return void */ public function build_bookable_slots(BookingRequest $booking_request, int $selectable_time_interval){ $this->slots = []; $this->work_minutes = []; foreach($this->work_time_periods as $time_period){ for($minute = $time_period->start_time; $minute <= $time_period->end_time - $booking_request->duration; $minute+= $selectable_time_interval){ $period = new TimePeriod(['start_time' => $minute - $booking_request->buffer_before, 'end_time' => $minute + $booking_request->duration + $booking_request->buffer_after]); $this->work_minutes[] = $minute; // add booking slot $slot = new BookingSlot(); $slot->start_date = $this->date; $slot->start_time = $minute; $slot->max_capacity = $this->get_max_capacity(); $slot->min_capacity_to_be_blocked = $this->get_min_capacity_to_be_blocked(); // --------------------- // LOGIC FOR "BOOKED" PERIODS // --------------------- foreach($this->booked_time_periods as $booked_period){ if ($booked_period->start_time_with_buffer() >= $period->end_time || $booked_period->end_time_with_buffer() <= $period->start_time) { // not intersected }else{ // intersects with a booked period, disqualify it if($booked_period->service_id != $this->service_id){ // if it's a different service being performed - block full capacity of a slot, because you can't share capacities between different services $slot->booked_capacity = $this->max_capacity; }else{ $slot->booked_capacity+= $booked_period->total_attendees; } } } // --------------------- // LOGIC FOR "BLOCKED" PERIODS // --------------------- foreach($this->blocked_time_periods as $blocked_period){ if ($blocked_period->start_time >= $period->end_time || $blocked_period->end_time <= $period->start_time) { // not intersected }else{ // intersects with a blocked period, disqualify it $slot->booked_capacity = $slot->max_capacity; } } $this->slots[] = $slot; } } } // Function to print the intersection private function find_intersection(array $time_periods){ // First interval $start = $time_periods[0]->start_time; $end = $time_periods[0]->end_time; // Check rest of the intervals and find the intersection $total = count($time_periods); for ($i = 1; $i < $total; $i++){ // If no intersection exists if ($time_periods[$i]->start_time > $end || $time_periods[$i]->end_time < $start){ return []; }else{ // Else update the intersection $start = max($start, $time_periods[$i]->start_time); $end = min($end, $time_periods[$i]->end_time); } } return new TimePeriod(['start_time' => $start, 'end_time' => $end]); } public function add_blocked_period(BlockedPeriod $blocked_period){ $this->blocked_time_periods[] = $blocked_period; } public function add_booked_period(BookedPeriod $booked_period, $block_full_capacity = false){ if($block_full_capacity){ $max_capacity_booked_period = clone $booked_period; $max_capacity_booked_period->total_attendees = $this->max_capacity; $this->booked_time_periods[] = $max_capacity_booked_period; }else{ $this->booked_time_periods[] = $booked_period; } } public function add_available_periods(array $work_period_groups){ $available_periods = []; foreach($work_period_groups as $group_work_periods){ // loop through groups, if multiple group - add them by comparing them against each other $available_periods = ($available_periods) ? $this->compare_periods($available_periods, $group_work_periods) : $group_work_periods; } $this->work_time_periods = $available_periods; } private function compare_periods(array $available_periods, array $periods_to_intersect): array{ $intersects = []; foreach($available_periods as $available_period){ foreach($periods_to_intersect as $intersect_period){ $intersect = $this->find_intersection([$available_period, $intersect_period]); if($intersect) $intersects[] = $intersect; } } return $intersects; } public function intersect_time_period(TimePeriod $time_period){ } // TODO instead of trying to find overlaps, just add a period and then overlaps can be skipped using array_unique for available minutes public function add_time_period(TimePeriod $time_period_to_add){ $overlapped_time_periods = []; $not_overlapped_time_periods = []; // search for all periods that overlap, to later merge them into one foreach($this->work_time_periods as $time_period){ if($time_period->check_if_overlaps($time_period_to_add)){ $overlapped_time_periods[] = $time_period; }else{ $not_overlapped_time_periods[] = $time_period; } } if($overlapped_time_periods){ // if overlapping periods were found - find a unified range between them and create a new list of available time // periods which should include those that were not overlapped $this->work_time_periods = $not_overlapped_time_periods; $merged_time_period = TimePeriod::get_unified_period_from_overlapped_periods($merged_time_period); $this->work_time_periods[] = $merged_time_period; }else{ // nothing was overlapped, simply add this time period $this->work_time_periods[] = $time_period_to_add; } } public function generate_resource_id(): string{ return $this->agent_id.'_'.$this->service_id.'_'.$this->location_id; } public static function create_from_connection(\OsConnectorModel $connection): BookingResource{ $booking_request = new BookingResource([ 'agent_id' => $connection->agent_id, 'service_id' => $connection->service_id, 'location_id' => $connection->location_id]); $booking_request->retrieve_service_data(); return $booking_request; } public static function allowed_props(): array{ return ['agent_id', 'service_id', 'location_id', 'date', 'start_time', 'end_time']; } }