get('proIds'))) { $proIds = []; } else { $proIds = explode(',', $request->get('proIds')); } $start = $request->get('start'); $end = $request->get('end'); $clientId = $request->get('clientId'); $timeZone = $request->get('timeZone'); // get appointments $appointments = Appointment ::where('start_time', '>=', $start) ->where('start_time', '<=', $end); if(count($proIds)) { $appointments = $appointments ->where(function ($query) use ($proIds, $clientId) { $query ->whereIn('pro_id', $proIds) ->orWhere('client_id', '=', $clientId); }); } $appointments = $appointments->get(); $events = []; foreach ($appointments as $appointment) { $events[] = [ "type" => "appointment", "title" => ($appointment->client->id != $clientId ? '* ' : '') . $appointment->pro->displayName() . " (" . strtolower($appointment->status) . ")", "_title" => $appointment->title, "description" => $appointment->description, "clientName" => $appointment->client->displayName(), "appointmentUid" => $appointment->uid, "clientUid" => $appointment->client->uid, "proId" => $appointment->pro->id, "proUid" => $appointment->pro->uid, "start" => convertToTimezone($appointment->start_time, $timeZone), "end" => convertToTimezone($appointment->end_time, $timeZone), "clientOnly" => !in_array($appointment->pro->id, $proIds), "otherClient" => ($appointment->client->id != $clientId), "status" => $appointment->status, "editable" => true ]; } if(count($proIds)) { // get availability $genAvail = ProGeneralAvailability ::where('is_cancelled', false) ->whereIn('pro_id', $proIds) ->get(); $specAvail = ProSpecificAvailability ::where('is_cancelled', false) ->whereIn('pro_id', $proIds) ->where(function ($query) use ($start, $end) { $query ->where(function ($query2) use ($start, $end) { $query2 ->where('start_time', '>=', $start) ->where('start_time', '<=', $end); }) ->orWhere(function ($query2) use ($start, $end) { $query2 ->where('end_time', '>=', $start) ->where('end_time', '<=', $end); }); }) ->get(); $specUnavail = ProSpecificUnavailability ::where('is_cancelled', false) ->whereIn('pro_id', $proIds) ->where(function ($query) use ($start, $end) { $query ->where(function ($query2) use ($start, $end) { $query2 ->where('start_time', '>=', $start) ->where('start_time', '<=', $end); }) ->orWhere(function ($query2) use ($start, $end) { $query2 ->where('end_time', '>=', $start) ->where('end_time', '<=', $end); }); }) ->get(); // logic // 1. enumerate days between start and end (inclusive) // 2. for each pro // 3. calculate pairs of start-end of availability // 1. enumerate days between start and end (inclusive) $phpTZ = appTZtoPHPTZ($timeZone); $phpTZObject = new \DateTimeZone($phpTZ); $startDate = new \DateTime($start, $phpTZObject); $endDate = new \DateTime($end, $phpTZObject); $endDate->setTime(23, 59, 59); $period = new \DatePeriod($startDate, \DateInterval::createFromDateString('1 day'), $endDate); $days = []; foreach ($period as $day) { $days[] = [ "day" => strtoupper($day->format("l")), // SUNDAY, etc. "date" => $day->format("Y-m-d"), // 2020-10-04, etc. ]; } foreach ($proIds as $proId) { $proTimeLine = new TimeLine($startDate, $endDate); $pro = Pro::where('id', $proId)->first(); $proGenAvail = $genAvail->filter(function ($record) use ($proId) { return $record->pro_id == $proId; }); // if no gen avail, assume mon to fri, 9 to 7 if (!count($proGenAvail)) { $dayNames = ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY']; foreach ($dayNames as $dayName) { $item = new \stdClass(); $item->day_of_week = $dayName; $item->timezone = $timeZone; $item->start_time = '09:00:00'; $item->end_time = '18:00:00'; $proGenAvail->push($item); } } $proSpecAvail = $specAvail->filter(function ($record) use ($proId) { return $record->pro_id == $proId; }); $proSpecUnavail = $specUnavail->filter(function ($record) use ($proId) { return $record->pro_id == $proId; }); // general availability foreach ($days as $day) { $proGenAvailForTheDay = $proGenAvail->filter(function ($record) use ($day) { return $record->day_of_week === $day["day"]; }); foreach ($proGenAvailForTheDay as $ga) { $gaStart = new \DateTime($day["date"], new \DateTimeZone(appTZtoPHPTZ($ga->timezone))); $parts = explode(":", $ga->start_time); $gaStart->setTime(intval($parts[0]), intval($parts[1]), intval($parts[2])); $gaStart->setTimezone($phpTZObject); $gaEnd = new \DateTime($day["date"], new \DateTimeZone(appTZtoPHPTZ($ga->timezone))); $parts = explode(":", $ga->end_time); $gaEnd->setTime(intval($parts[0]), intval($parts[1]), intval($parts[2])); $gaEnd->setTimezone($phpTZObject); $proTimeLine->addAvailability($gaStart, $gaEnd); } } // specific availability foreach ($proSpecAvail as $sa) { $saStart = new \DateTime($sa->start_time, new \DateTimeZone(appTZtoPHPTZ($sa->timezone))); $saStart->setTimezone($phpTZObject); $saEnd = new \DateTime($sa->end_time, new \DateTimeZone(appTZtoPHPTZ($sa->timezone))); $saEnd->setTimezone($phpTZObject); $proTimeLine->addAvailability($saStart, $saEnd); } // specific unavailability foreach ($proSpecUnavail as $sua) { $suaStart = new \DateTime($sua->start_time, new \DateTimeZone(appTZtoPHPTZ($sua->timezone))); $suaStart->setTimezone($phpTZObject); $suaEnd = new \DateTime($sua->end_time, new \DateTimeZone(appTZtoPHPTZ($sua->timezone))); $suaEnd->setTimezone($phpTZObject); $proTimeLine->removeAvailability($suaStart, $suaEnd); } // make already booked slots unavailable $proAppointments = $appointments->filter(function ($record) use ($proId) { return $record->pro_id == $proId && !in_array($record->status, ['CANCELLED', 'COMPLETED', 'ABANDONED']); }); foreach ($proAppointments as $appointment) { if ($appointment->start_time && $appointment->end_time) { $appStart = convertToTimezone($appointment->start_time, $timeZone, 'UTC', true); $appEnd = convertToTimezone($appointment->end_time, $timeZone, 'UTC', true); $proTimeLine->removeAvailability($appStart, $appEnd); } } foreach ($proTimeLine->available as $item) { $eStart = new \DateTime('@' . $item->start); $eStart->setTimezone($phpTZObject); $eEnd = new \DateTime('@' . $item->end); $eEnd->setTimezone($phpTZObject); $events[] = [ "type" => "availability", "title" => $pro->displayName() . " (available)", "proId" => $pro->id, "proUid" => $pro->uid, "start" => $eStart->format('Y-m-d H:i:s'), "end" => $eEnd->format('Y-m-d H:i:s'), "editable" => false ]; } } } return json_encode($events); } }