268 lines
9.5 KiB
PHP
268 lines
9.5 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Http\Resources\TimeslotSettingResource;
|
|
use Carbon\Carbon;
|
|
use Carbon\CarbonImmutable;
|
|
use Carbon\CarbonInterval;
|
|
use Carbon\CarbonPeriod;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Kwaadpepper\Enum\Traits\CastsEnums;
|
|
use LDC\LDCCRM\Enums\Country;
|
|
use Propaganistas\LaravelPhone\Casts\E164PhoneNumberCast;
|
|
|
|
class Shop extends Model
|
|
{
|
|
use CastsEnums;
|
|
use HasFactory;
|
|
|
|
/**
|
|
* The attributes that should be cast to native types.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $casts = [
|
|
'phone' => 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<string,boolean> $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<string,boolean> 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<string,boolean> */
|
|
$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;
|
|
}
|
|
}
|