ldc-boutique-cnc-sable/app/Jobs/UpsertShopsPickupExceptionsUsingApiForYear.php
2026-10-27 09:58:12 +01:00

117 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
);
}
}