E164PhoneNumberCast::class . ':FR' ]; /** * Map attribute names to enum classes. * * @var array */ protected $enumCasts = [ 'country' => Country::class ]; /** * The model's attributes. * * @var array */ protected $attributes = [ 'phone_country' => 'FR' ]; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'address', 'postal_code', 'city', 'country', 'phone', 'latitude', 'longitude', 'gmaps_placeid', 'gmaps_link_href', 'gmaps_embed_href', ]; // * RELATIONSHIPS /** * hasMany timeslot settings relation * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function timeslotSettings() { return $this->hasMany(TimeslotSetting::class); } /** * hasMany collection exceptions relation * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function collectionExceptions() { return $this->hasMany(CollectionException::class); } /** * hasMany timeslots relation * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function timeslots() { return $this->hasMany(Timeslot::class); } // * METHODS /** * Check if creneau is available * * @param \Carbon\Carbon $creneau * @return boolean */ public function isCreneauAvailable(Carbon $creneau): bool { $days_to_come = Carbon::now()->diffInDays($creneau) + 1; $creneaux = $this->getAvailableCreneaux($days_to_come); return $creneaux[$creneau->format('d/m/Y')][$creneau->format('h:i')] ?? false; } /** * Récupérer tout les créneaux disponible à partir de * maintenant pour un magasin sur les jours à venir. * * @param integer $days_to_come Sur quelle période proposer des créneaux à partir de maintenant. * @return array * @throws \RuntimeException Si la période demandé est incohérente. */ public function getAvailableCreneaux(int $days_to_come = 1): array { if ($days_to_come < 1) { throw new \RuntimeException('Impossible de proposer des créneaux avec une période inférieure a 1 jour.'); } /** @var integer $days_to_come */ $days_to_come = \min($days_to_come, 15); /** @var \Illuminate\Database\Eloquent\Collection<\App\Models\TimeslotSetting> */ $timeslotSettings = $this->timeslotSettings; $plage = []; foreach ($timeslotSettings as $timeslot) { $timeslotSetting = (object)(new TimeslotSettingResource($timeslot))->toArray(request()); $plage[$timeslot->day->value] = $timeslotSetting; } // * Génération des plages. /** @var \Carbon\CarbonImmutable $today Le jour de la semaine actuelle a minuit. */ $today = CarbonImmutable::now()->startOfDay(); /** @var \Carbon\CarbonPeriod La période pour laquelle il faut fournir les crénaux. */ $period = CarbonPeriod::dates($today, $today->add($days_to_come, 'day')); /** @var \Illuminate\Database\Eloquent\Collection<\Carbon\Carbon> Jours de fermeture exceptionnels.*/ $noCollectDays = $this->collectionExceptions() ->whereDate('date', '>=', $period->start) ->whereDate('date', '<=', $period->end) ->orderBy('date')->pluck('date')->keyBy(function (Carbon $date) { return $date->format('d/m/Y'); }); $availCreneaux = []; /** @var \Carbon\CarbonInterface|null $day_to_consider Le jour pour lequel il faut obtenir les créneaux. */ foreach ($period->days() as $day_to_consider) { // * Convert carbon weekday to FR $day_to_consider_week_day = $day_to_consider->weekday() ?: 7; // * Ignorer jours de fermeture. if ($noCollectDays->has($day_to_consider->format('d/m/Y'))) { continue; } // * Délais de préparation minimal pour aujourd'hui. /** @var string $min_order_preparation A string like "hh:ii". Durée de préparation de commande */ $min_order_preparation = $plage[$day_to_consider_week_day]->min_order_preparation; /** @var \Carbon\CarbonInterval $min_order_preparation_carbon Heures minute de délais de préparation */ $min_order_preparation_carbon = CarbonInterval::createFromFormat( 'h:i', $min_order_preparation ); /** @var integer|null $max_orders_per_interval . Nb de réservations maximum par créneau */ $max_orders_per_interval = $plage[$day_to_consider_week_day]->max_orders_per_interval; /** @var \Carbon\CarbonInterval $baseInterval Durée d'un créneau */ $baseInterval = CarbonInterval::createFromFormat('h:i', $plage[$day_to_consider_week_day]->interval); $availCreneaux[$day_to_consider->format('d/m/Y')] = []; foreach (['morning', 'afternoon'] as $half_day_period) { if (!empty($plage[$day_to_consider_week_day]->{"{$half_day_period}_start"})) { /** @var \Carbon\Carbon Début de la plage de demi journée */ $start_half_day = Carbon::createFromTimeString( $plage[$day_to_consider_week_day]->{"{$half_day_period}_start"} ); /** @var \Carbon\Carbon Fin de la plage de demi journée */ $end_half_day = Carbon::createFromTimeString( $plage[$day_to_consider_week_day]->{"{$half_day_period}_end"} ); /** @var \Carbon\CarbonPeriod La période de demi journée a considérer */ $half_day_period = CarbonPeriod::dates($start_half_day, $end_half_day); /** @var array $crenaux Les créneaux et leur disponibilité. */ $crenaux = $this->addCreneaux( CarbonImmutable::parse($day_to_consider), $half_day_period, $min_order_preparation_carbon, $max_orders_per_interval, $baseInterval ); $availCreneaux[$day_to_consider->format('d/m/Y')] = array_merge( $availCreneaux[$day_to_consider->format('d/m/Y')], $crenaux ); } //end if } //end foreach } //end foreach return $availCreneaux; } /** * Calcule les disponibilités de créneaux d'un magasin * pour une période et un jour donné. * * @param \Carbon\CarbonImmutable $actual_day Le jour à considérer. * @param \Carbon\CarbonPeriod $half_day_period La période de demi journée à considérer. * @param \Carbon\CarbonInterval $min_order_preparation Durée de préparation d'une commande. * @param integer|null $max_orders_per_interval Nombre de commandes max par intervalles. * @param \Carbon\CarbonInterval $interval_duration La durée d'un intervalle. * @return array Tableau avec en index l'heure minute et une valeur booléenne de disponibilité. */ private function addCreneaux( CarbonImmutable $actual_day, CarbonPeriod $half_day_period, CarbonInterval $min_order_preparation, $max_orders_per_interval, CarbonInterval $interval_duration ): array { /** @var array */ $out = []; foreach ($half_day_period->interval($interval_duration) as $creneau) { /** @var integer Le nombre de créneaux déja réservés */ $nbtakens = $this->timeslots() ->where('slot', Carbon::createFromFormat( 'd/m/Y H:i', "{$actual_day->format('d/m/Y')} {$creneau->format('H:i')}" ))->count(); // * Le créneau est-il disponible ? $is_dispo = true; if (!\is_null($max_orders_per_interval)) { $is_dispo = $nbtakens < $max_orders_per_interval; } // * Délais minimal avant qu'ils soit possible de proposer un créneau. $minimal_prepartion_offset_time = CarbonImmutable::now()->add($min_order_preparation); // * Le créneaux n'est pas disponible si il dépasse le temp minimal de préparation à partir de maintenant. if ( $actual_day->addHours($creneau->format('H')) ->addMinutes($creneau->format('i')) < $minimal_prepartion_offset_time ) { $is_dispo = false; } $out[$creneau->format('H:i')] = $is_dispo; } //end foreach return $out; } }