<?php
namespace App\Service;
use Throwable;
use Exception;
use App\Adapter\AppointmentAdapter;
use App\Mapper\AppointmentMapper;
use App\Model\Appointment;
use App\Model\Slot;
use App\Utils\Date;
use App\Utils\Utils;
use DateInterval;
use DatePeriod;
use DateTime;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
class SlotService extends AppointmentService
{
private AppointmentService $appointmentService;
public function __construct(AppointmentAdapter $adapter,
AppointmentMapper $mapper,
ClientService $clientService,
SerializerInterface $serializer,
AppointmentService $appointmentService
)
{
parent::__construct($adapter, $mapper, $clientService, $serializer);
$this->appointmentService = $appointmentService;
}
/**
* @throws Throwable
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
*/
public function getAvailability(array $data, Appointment $appointment): ?array
{
if (!isset($data['serviceIDs']) || !isset($data['garageID'])) {
throw new BadRequestHttpException('Bad request : serviceIDs or garageID are empty ');
}
$availabilities = $this->adapter->getServiceAvailability($data);
$slots = $this->getFormatedCalendarDate($availabilities);
$hours = [];
foreach ($slots as $day => $slot) {
$hourSlots = $this->getHoursSlots($slots, $day, $appointment);
if (!empty($hourSlots)) {
$hours[$day] = $hourSlots;
}
}
dump($hours);
return $hours;
}
/**
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws Throwable
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function getOtherAvailabilities(Appointment $appointment): ?array
{
$garages = $this->getGarages();
$idCurrent = $appointment->getGarage()->getId();
$otherGarages = [];
foreach ($garages as $garage) {
$gid = $garage['id'];
if ($gid !== $idCurrent) {
$data = [
'serviceIDs' => implode(',', $this->getServiceIds($appointment)),
'garageID' => $gid,
];
$availabilities = $this->getAvailability($data, $appointment);
if (!empty($availabilities)) {
$garage['availabilities'] = $availabilities;
$otherGarages[$garage['id']] = $garage;
}
}
}
return $this->sortGaragesByAvailabilities($otherGarages);
}
/**
* Sort garages: put the garage with the closest availability in first.
*/
public function sortGaragesByAvailabilities(array $garages): array
{
uasort($garages, function ($a, $b) {
if (empty($a['availabilities']) || empty($b['availabilities'])) {
return 0;
}
$datea = array_keys($a['availabilities'])[0];
$dateb = array_keys($b['availabilities'])[0];
return $datea <=> $dateb;
});
return $garages;
}
/**
* @return array
*
* @throws Exception
*/
public function getDropPickUpSlot(Appointment $appointment, array $selectedDate, bool $isReception = true)
{
// reception slot
$data = [
'serviceIDs' => implode(',', $this->getServiceIds($appointment)),
'garageID' => $appointment->getGarage()->getId(),
'from' => $selectedDate['from'],
'to' => $selectedDate['to'],
];
$slots = $this->adapter->getDropPickupSlot($data, $isReception);
$formatedSlots = $this->getFormatedCalendarDate($slots);
$reception = [];
$day = $selectedDate['day'];
if (key_exists($day, $formatedSlots)) { // Get all slots time corresponding to the selected day
$times = explode(' - ', $selectedDate['hour']);
foreach ($formatedSlots[$day] as $hour) {
if ($isReception) {
if ($hour['end'] < $times[0]) {
$reception[] = $hour;
}
} else {
if ($hour['start'] > $times[1]) {
$reception[] = $hour;
}
}
}
}
return $reception;
}
public function getFormatedCalendarDate(?array $availabilities): ?array
{
$slots = array_map(
function ($value) {
$date = [];
$date['start'] = Utils::getFormatedDate($value['start']);
$date['end'] = Utils::getFormatedDate($value['end']);
return $date;
},
$availabilities
);
return Utils::getCalendarFormatedSlots($slots);
}
public function saveSlots(Appointment &$appointment, array $timeSlot, array $receptionSlot)
{
// slot
$slot = new Slot();
$this->hydrateSlot($slot, $timeSlot['day'], $timeSlot['time']);
$appointment->setSlot($slot);
// reception slot
$this->setAppointmentDeliverPickSlot($receptionSlot, $timeSlot, $appointment);
// delivery slot
$this->setAppointmentDeliverPickSlot($receptionSlot, $timeSlot, $appointment, false);
}
protected function hydrateSlot(Slot $slot, string $day, $sTimes)
{
$times = explode(' - ', $sTimes);
$startTime = new DateTime($day . ' ' . $times[0]);
$slot->setStart($startTime->getTimestamp());
if (count($times) > 1) {
$endTime = new DateTime($day . ' ' . $times[1]);
$slot->setEnd($endTime->getTimestamp());
}
}
public function handleRequest(Request $request, array $timeSlot, array $responseDatas): array
{
$receptionTime = null;
$deliveryTime = null;
$garageID = null;
/** @var Appointment $appointment */
$session = $request->getSession();
$appointment = $session->get('appointment');
if ($appointment->getSlot()) { // update from resume
$garageID = $appointment->getGarage()->getId();
$slot = $appointment->getSlot();
$timeSlot['day'] = Date::getReadableDayFromSlot($slot);
$timeSlot['time'] = Date::getReadableTimeFromSlot($slot);
$receptionTime = Date::getReadableTimeFromSlot($appointment->getReceptionSlot());
$deliveryTime = Date::getReadableTimeFromSlot($appointment->getDeliverySlot());
}
if ($request->get('garageID')) {
$garageID = $request->get('garageID');
}
$responseDatas = array_merge([
'receptionTime' => [],
'deliveryTime' => [],
'hours' => [],
], $responseDatas);
if ($garageID) { // on change garage
$responseDatas['day'] = $timeSlot['day'];
$responseDatas['time'] = $timeSlot['time'];
$this->appointmentService->changeGarage($garageID, $session, $responseDatas, $this);
}
$this->appointmentService->getDatasFromServices($appointment, $responseDatas);
// $otherGarages = $this->getOtherAvailabilities($appointment);
$otherGarages= [];
//for timeline
$serviceHeaderInfo = $this->appointmentService->getServiceHeaderInfo($appointment);
return array_merge([
'otherSlots' => $otherGarages,
'day' => $timeSlot['day'],
'time' => $timeSlot['time'],
'garages' => array_values($otherGarages),
'current' => $appointment->getGarage(),
'selectedReceptionTime' => $receptionTime,
'selectedDeliveryTime' => $deliveryTime,
'garageID' => $garageID,
'appointment' => $appointment
], $responseDatas);
}
/**
* @param array $slots
* @param array $timeSlot
* @param Appointment $appointment
* @param bool $isReception
*/
public function setAppointmentDeliverPickSlot(array $slots, array $timeSlot, Appointment $appointment, bool $isReception = true)
{
$type = $isReception ? 'reception' : 'delivery';
$reception = new Slot();
if (!$slots[$type]) {
$times = explode(' - ', $timeSlot['time']);
$slots[$type] = $isReception ? $times[0] : $times[1];
}
$this->hydrateSlot($reception, $timeSlot['day'], $slots[$type]);
$appointment->{'set' . $type . 'Slot'}($reception);
}
/**
* @param array $slots
* @param string $day
* @param Appointment $appointment
* @return array
* @throws Exception
*/
public function getHoursSlots(array $slots, string $day, Appointment $appointment): array
{
$duration = $this->appointmentService->getTotalServiceDuration($appointment->getServices());
$hours = [];
foreach ($slots[$day] as $date) {
$start = new DateTime($date['start']);
$interval = new DateInterval('PT' . $duration . 'M');
$end = new DateTime($date['end']);
$period = new DatePeriod($start, $interval, $end);
foreach ($period as $item) {
$hour_start = $item->format('H:i');
$hour_end = $item->add($interval)->format('H:i');
if (Date::isFutureDate($day . ' ' . $hour_start) && $hour_end <= $date['end']) {
$hours[] = [
'start' => $hour_start,
'end' => $hour_end
];
}
}
}
return $hours;
}
}