Files
orderPRO/src/Modules/Cron/ProductLinksHealthCheckHandler.php

141 lines
5.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Modules\Cron;
use App\Modules\ProductLinks\ChannelOffersRepository;
use App\Modules\ProductLinks\OfferImportService;
use App\Modules\ProductLinks\ProductLinksRepository;
use App\Modules\Settings\IntegrationRepository;
use Throwable;
final class ProductLinksHealthCheckHandler
{
private const ALERT_TYPE = 'missing_remote_link';
private const ALERT_MESSAGE = 'Powiazanie nie istnieje juz po stronie zewnetrznej.';
public function __construct(
private readonly IntegrationRepository $integrations,
private readonly OfferImportService $offerImportService,
private readonly ProductLinksRepository $links,
private readonly ChannelOffersRepository $offers
) {
}
/**
* @param array<string, mixed> $payload
* @param array<string, mixed> $job
* @return array<string, mixed>
*/
public function __invoke(array $payload = [], array $job = []): array
{
$forcedIntegrationId = max(0, (int) ($payload['integration_id'] ?? 0));
$activeIntegrations = array_values(array_filter(
$this->integrations->listByType('shoppro'),
static function (array $integration) use ($forcedIntegrationId): bool {
$id = (int) ($integration['id'] ?? 0);
if ($forcedIntegrationId > 0 && $id !== $forcedIntegrationId) {
return false;
}
return $id > 0
&& ($integration['is_active'] ?? false) === true
&& ($integration['has_api_key'] ?? false) === true;
}
));
if ($activeIntegrations === []) {
return [
'ok' => true,
'message' => 'Brak aktywnych integracji z kluczem API do weryfikacji powiazan.',
'checked_links' => 0,
'missing_links' => 0,
'integrations' => 0,
'integration_failures' => 0,
];
}
$checkedLinks = 0;
$missingLinks = 0;
$resolvedAlerts = 0;
$integrationFailures = 0;
$errors = [];
$checkedAt = date('Y-m-d H:i:s');
foreach ($activeIntegrations as $integration) {
$integrationId = (int) ($integration['id'] ?? 0);
if ($integrationId <= 0) {
continue;
}
try {
$credentials = $this->integrations->findApiCredentials($integrationId);
} catch (Throwable $exception) {
$integrationFailures++;
if (count($errors) < 5) {
$errors[] = 'Integracja #' . $integrationId . ': ' . $exception->getMessage();
}
continue;
}
if ($credentials === null || trim((string) ($credentials['api_key'] ?? '')) === '') {
$integrationFailures++;
if (count($errors) < 5) {
$errors[] = 'Integracja #' . $integrationId . ': brak poprawnych danych API.';
}
continue;
}
$import = $this->offerImportService->importShopProOffers($credentials);
if (($import['ok'] ?? false) !== true) {
$integrationFailures++;
if (count($errors) < 5) {
$errors[] = 'Integracja #' . $integrationId . ': ' . trim((string) ($import['message'] ?? 'Blad importu ofert.'));
}
continue;
}
$links = $this->links->listActiveLinksForMissingCheck($integrationId);
foreach ($links as $link) {
$mapId = (int) ($link['id'] ?? 0);
$externalProductId = trim((string) ($link['external_product_id'] ?? ''));
$externalVariantId = $this->nullableText($link['external_variant_id'] ?? null);
if ($mapId <= 0 || $externalProductId === '') {
continue;
}
$checkedLinks++;
$offer = $this->offers->findByExternalIdentity($integrationId, $externalProductId, $externalVariantId);
if ($offer === null) {
$missingLinks++;
$this->links->upsertActiveAlert($mapId, self::ALERT_TYPE, self::ALERT_MESSAGE, $checkedAt);
continue;
}
$this->links->resolveActiveAlert($mapId, self::ALERT_TYPE, $checkedAt);
$resolvedAlerts++;
}
}
return [
'ok' => $integrationFailures === 0,
'message' => $integrationFailures === 0
? 'Weryfikacja powiazan zakonczona.'
: 'Weryfikacja zakonczona z bledami integracji.',
'checked_links' => $checkedLinks,
'missing_links' => $missingLinks,
'resolved_alerts' => $resolvedAlerts,
'integrations' => count($activeIntegrations),
'integration_failures' => $integrationFailures,
'errors' => $errors,
];
}
private function nullableText(mixed $value): ?string
{
$text = trim((string) $value);
return $text === '' ? null : $text;
}
}