118 lines
3.4 KiB
PHP
118 lines
3.4 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace App\Jobs;
|
||
|
|
|
||
|
|
use App\Models\PickupException;
|
||
|
|
use App\Models\Shop;
|
||
|
|
use Domain\Enums\RepeatFrequency;
|
||
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||
|
|
use Illuminate\Foundation\Queue\Queueable;
|
||
|
|
use Illuminate\Support\Arr;
|
||
|
|
use Illuminate\Support\Facades\Date;
|
||
|
|
use Illuminate\Support\Facades\Http;
|
||
|
|
use Illuminate\Support\Facades\Validator;
|
||
|
|
|
||
|
|
class UpsertShopsPickupExceptionsUsingApiForYear implements ShouldQueue
|
||
|
|
{
|
||
|
|
use Queueable;
|
||
|
|
|
||
|
|
private const string CALENDAR_API_URL = 'https://calendrier.api.gouv.fr/jours-feries/metropole/:year.json';
|
||
|
|
|
||
|
|
private const int API_CALL_TIMEOUT = 120;
|
||
|
|
|
||
|
|
protected int $year;
|
||
|
|
|
||
|
|
public function __construct(?int $year = null)
|
||
|
|
{
|
||
|
|
$this->year = $year ?? Date::now()->addYear()->year;
|
||
|
|
|
||
|
|
throw_if(
|
||
|
|
$this->year < 2026,
|
||
|
|
\InvalidArgumentException::class
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function handle(): void
|
||
|
|
{
|
||
|
|
$hollydayList = $this->getHollydaysForYear($this->year);
|
||
|
|
|
||
|
|
foreach ($hollydayList as $holiday) {
|
||
|
|
$exception = PickupException::query()
|
||
|
|
->firstOrNew(['label' => $holiday->label]);
|
||
|
|
$exception->fill([
|
||
|
|
'date' => $holiday->date->format('Y-m-d'),
|
||
|
|
'repeatable' => true,
|
||
|
|
'repeat_frequency' => RepeatFrequency::YEARLY,
|
||
|
|
]);
|
||
|
|
$exception->setAttribute('auto_updated', true);
|
||
|
|
$exception->save();
|
||
|
|
}
|
||
|
|
|
||
|
|
// * Sync repeatables api pickup exception for all shops
|
||
|
|
$repeatablesFromApi = PickupException::query()
|
||
|
|
->whereIn('label', array_map(
|
||
|
|
fn ($v) => $v->label,
|
||
|
|
$hollydayList
|
||
|
|
))->get();
|
||
|
|
|
||
|
|
Shop::query()->get()
|
||
|
|
->each(fn ($shop) => $shop->pickupExceptions()
|
||
|
|
->syncWithoutDetaching($repeatablesFromApi));
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @return object{date:\DateTimeInterface,label:string}[] */
|
||
|
|
private function getHollydaysForYear(int $year): array
|
||
|
|
{
|
||
|
|
$url = $this->getCalendarApiUrlFor($year);
|
||
|
|
|
||
|
|
$response = Http::timeout(self::API_CALL_TIMEOUT)->get($url);
|
||
|
|
throw_unless(
|
||
|
|
$response->successful(),
|
||
|
|
\UnexpectedValueException::class,
|
||
|
|
'Failed to fetch calendar from api'
|
||
|
|
);
|
||
|
|
|
||
|
|
$outputList = $response
|
||
|
|
->collect()
|
||
|
|
->map(fn ($label, $date) => [
|
||
|
|
'date' => $date,
|
||
|
|
'label' => $label,
|
||
|
|
])->all();
|
||
|
|
|
||
|
|
$rules = [
|
||
|
|
'*' => 'array',
|
||
|
|
'*.date' => ['required', 'date'],
|
||
|
|
'*.label' => ['required', 'string'],
|
||
|
|
];
|
||
|
|
|
||
|
|
$validator = Validator::make($outputList, $rules);
|
||
|
|
|
||
|
|
if ($validator->fails()) {
|
||
|
|
$errors = $validator->errors();
|
||
|
|
throw new \UnexpectedValueException(
|
||
|
|
'Invalid response: ' .
|
||
|
|
json_encode($errors->messages())
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return array_map(fn (array $entry) => (object) [
|
||
|
|
'date' => Date::parse(
|
||
|
|
$entry['date'],
|
||
|
|
new \DateTimeZone('Europe/Paris')
|
||
|
|
),
|
||
|
|
'label' => Arr::string($entry, 'label'),
|
||
|
|
], $outputList);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getCalendarApiUrlFor(int $year): string
|
||
|
|
{
|
||
|
|
return str_replace(
|
||
|
|
':year',
|
||
|
|
strval($year),
|
||
|
|
self::CALENDAR_API_URL
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|