ldc-boutique-cnc/app/Models/Shop.php
2023-02-21 09:39:46 +01:00

266 lines
9.5 KiB
PHP

<?php
namespace App\Models;
use App\Http\Resources\TimeslotSettingResource;
use App\Traits\Models\HasCoordinates;
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;
use HasCoordinates;
/**
* 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',
'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;
}
}