feat: Implement Allegro Order Sync and Status Management
- Added AllegroOrderSyncStateRepository for managing sync state with Allegro orders. - Introduced AllegroOrdersSyncService to handle the synchronization of orders from Allegro. - Created AllegroStatusDiscoveryService to discover and store order statuses from Allegro. - Developed AllegroStatusMappingRepository for managing status mappings between Allegro and OrderPro. - Implemented AllegroStatusSyncService to facilitate status synchronization. - Added CronSettingsController for managing cron job settings related to Allegro integration.
This commit is contained in:
703
src/Modules/Settings/AllegroIntegrationController.php
Normal file
703
src/Modules/Settings/AllegroIntegrationController.php
Normal file
@@ -0,0 +1,703 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Settings;
|
||||
|
||||
use App\Core\Http\Request;
|
||||
use App\Core\Http\Response;
|
||||
use App\Core\I18n\Translator;
|
||||
use App\Core\Security\Csrf;
|
||||
use App\Core\Support\Flash;
|
||||
use App\Core\View\Template;
|
||||
use App\Modules\Auth\AuthService;
|
||||
use App\Modules\Cron\CronRepository;
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
final class AllegroIntegrationController
|
||||
{
|
||||
private const OAUTH_STATE_SESSION_KEY = 'allegro_oauth_state';
|
||||
private const ORDERS_IMPORT_JOB_TYPE = 'allegro_orders_import';
|
||||
private const STATUS_SYNC_JOB_TYPE = 'allegro_status_sync';
|
||||
private const ORDERS_IMPORT_DEFAULT_INTERVAL_SECONDS = 300;
|
||||
private const ORDERS_IMPORT_DEFAULT_PRIORITY = 20;
|
||||
private const ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS = 3;
|
||||
private const STATUS_SYNC_DIRECTION_ALLEGRO_TO_ORDERPRO = 'allegro_to_orderpro';
|
||||
private const STATUS_SYNC_DIRECTION_ORDERPRO_TO_ALLEGRO = 'orderpro_to_allegro';
|
||||
private const STATUS_SYNC_DEFAULT_INTERVAL_MINUTES = 15;
|
||||
private const ORDERS_IMPORT_DEFAULT_PAYLOAD = [
|
||||
'max_pages' => 5,
|
||||
'page_limit' => 50,
|
||||
'max_orders' => 200,
|
||||
];
|
||||
private const OAUTH_SCOPES = [
|
||||
AllegroOAuthClient::ORDERS_READ_SCOPE,
|
||||
AllegroOAuthClient::SALE_OFFERS_READ_SCOPE,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly Template $template,
|
||||
private readonly Translator $translator,
|
||||
private readonly AuthService $auth,
|
||||
private readonly AllegroIntegrationRepository $repository,
|
||||
private readonly AllegroStatusMappingRepository $statusMappings,
|
||||
private readonly OrderStatusRepository $orderStatuses,
|
||||
private readonly CronRepository $cronRepository,
|
||||
private readonly AllegroOAuthClient $oauthClient,
|
||||
private readonly AllegroOrderImportService $orderImportService,
|
||||
private readonly AllegroStatusDiscoveryService $statusDiscoveryService,
|
||||
private readonly string $appUrl
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$settings = $this->repository->getSettings();
|
||||
$tab = trim((string) $request->input('tab', 'integration'));
|
||||
if (!in_array($tab, ['integration', 'statuses', 'settings'], true)) {
|
||||
$tab = 'integration';
|
||||
}
|
||||
$defaultRedirectUri = $this->defaultRedirectUri();
|
||||
|
||||
if (trim((string) ($settings['redirect_uri'] ?? '')) === '') {
|
||||
$settings['redirect_uri'] = $defaultRedirectUri;
|
||||
}
|
||||
$importIntervalSeconds = $this->currentImportIntervalSeconds();
|
||||
$statusSyncDirection = $this->currentStatusSyncDirection();
|
||||
$statusSyncIntervalMinutes = $this->currentStatusSyncIntervalMinutes();
|
||||
|
||||
$html = $this->template->render('settings/allegro', [
|
||||
'title' => $this->translator->get('settings.allegro.title'),
|
||||
'activeMenu' => 'settings',
|
||||
'activeSettings' => 'allegro',
|
||||
'user' => $this->auth->user(),
|
||||
'csrfToken' => Csrf::token(),
|
||||
'settings' => $settings,
|
||||
'activeTab' => $tab,
|
||||
'importIntervalSeconds' => $importIntervalSeconds,
|
||||
'statusSyncDirection' => $statusSyncDirection,
|
||||
'statusSyncIntervalMinutes' => $statusSyncIntervalMinutes,
|
||||
'statusMappings' => $this->statusMappings->listMappings(),
|
||||
'orderproStatuses' => $this->orderStatuses->listStatuses(),
|
||||
'defaultRedirectUri' => $defaultRedirectUri,
|
||||
'errorMessage' => (string) Flash::get('settings_error', ''),
|
||||
'successMessage' => (string) Flash::get('settings_success', ''),
|
||||
'warningMessage' => (string) Flash::get('settings_warning', ''),
|
||||
], 'layouts/app');
|
||||
|
||||
return Response::html($html);
|
||||
}
|
||||
|
||||
public function save(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
$environment = trim((string) $request->input('environment', 'sandbox'));
|
||||
if (!in_array($environment, ['sandbox', 'production'], true)) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.validation.environment_invalid'));
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
$clientId = trim((string) $request->input('client_id', ''));
|
||||
if ($clientId !== '' && mb_strlen($clientId) > 128) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.validation.client_id_too_long'));
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
$redirectUriInput = trim((string) $request->input('redirect_uri', ''));
|
||||
$redirectUri = $redirectUriInput !== '' ? $redirectUriInput : $this->defaultRedirectUri();
|
||||
if (!$this->isValidHttpUrl($redirectUri)) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.validation.redirect_uri_invalid'));
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
$ordersFetchStartDate = trim((string) $request->input('orders_fetch_start_date', ''));
|
||||
if ($ordersFetchStartDate !== '' && !$this->isValidDate($ordersFetchStartDate)) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.validation.orders_fetch_start_date_invalid'));
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->repository->saveSettings([
|
||||
'environment' => $environment,
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => trim((string) $request->input('client_secret', '')),
|
||||
'redirect_uri' => $redirectUri,
|
||||
'orders_fetch_enabled' => (string) $request->input('orders_fetch_enabled', '0') === '1',
|
||||
'orders_fetch_start_date' => $ordersFetchStartDate,
|
||||
]);
|
||||
Flash::set('settings_success', $this->translator->get('settings.allegro.flash.saved'));
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set(
|
||||
'settings_error',
|
||||
$this->translator->get('settings.allegro.flash.save_failed') . ' ' . $exception->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
public function saveImportSettings(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
$intervalMinutesRaw = (int) $request->input('orders_import_interval_minutes', 5);
|
||||
$intervalMinutes = max(1, min(1440, $intervalMinutesRaw));
|
||||
if ($intervalMinutesRaw !== $intervalMinutes) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.validation.orders_import_interval_invalid'));
|
||||
return Response::redirect('/settings/integrations/allegro?tab=settings');
|
||||
}
|
||||
|
||||
$statusSyncDirection = trim((string) $request->input(
|
||||
'status_sync_direction',
|
||||
self::STATUS_SYNC_DIRECTION_ALLEGRO_TO_ORDERPRO
|
||||
));
|
||||
if (!in_array($statusSyncDirection, $this->allowedStatusSyncDirections(), true)) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.validation.status_sync_direction_invalid'));
|
||||
return Response::redirect('/settings/integrations/allegro?tab=settings');
|
||||
}
|
||||
|
||||
$statusSyncIntervalRaw = (int) $request->input(
|
||||
'status_sync_interval_minutes',
|
||||
self::STATUS_SYNC_DEFAULT_INTERVAL_MINUTES
|
||||
);
|
||||
$statusSyncInterval = max(1, min(1440, $statusSyncIntervalRaw));
|
||||
if ($statusSyncIntervalRaw !== $statusSyncInterval) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.validation.status_sync_interval_invalid'));
|
||||
return Response::redirect('/settings/integrations/allegro?tab=settings');
|
||||
}
|
||||
|
||||
$existing = $this->findImportSchedule();
|
||||
$priority = (int) ($existing['priority'] ?? self::ORDERS_IMPORT_DEFAULT_PRIORITY);
|
||||
$maxAttempts = (int) ($existing['max_attempts'] ?? self::ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS);
|
||||
$payload = is_array($existing['payload'] ?? null)
|
||||
? (array) $existing['payload']
|
||||
: self::ORDERS_IMPORT_DEFAULT_PAYLOAD;
|
||||
$enabled = array_key_exists('enabled', $existing)
|
||||
? (bool) $existing['enabled']
|
||||
: true;
|
||||
$statusSchedule = $this->findStatusSyncSchedule();
|
||||
$statusPriority = (int) ($statusSchedule['priority'] ?? self::ORDERS_IMPORT_DEFAULT_PRIORITY);
|
||||
$statusMaxAttempts = (int) ($statusSchedule['max_attempts'] ?? self::ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS);
|
||||
$statusEnabled = array_key_exists('enabled', $statusSchedule)
|
||||
? (bool) $statusSchedule['enabled']
|
||||
: true;
|
||||
|
||||
try {
|
||||
$this->cronRepository->upsertSchedule(
|
||||
self::ORDERS_IMPORT_JOB_TYPE,
|
||||
$intervalMinutes * 60,
|
||||
$priority,
|
||||
$maxAttempts,
|
||||
$payload,
|
||||
$enabled
|
||||
);
|
||||
$this->cronRepository->upsertSchedule(
|
||||
self::STATUS_SYNC_JOB_TYPE,
|
||||
$statusSyncInterval * 60,
|
||||
$statusPriority,
|
||||
$statusMaxAttempts,
|
||||
null,
|
||||
$statusEnabled
|
||||
);
|
||||
$this->cronRepository->upsertSetting('allegro_status_sync_direction', $statusSyncDirection);
|
||||
$this->cronRepository->upsertSetting('allegro_status_sync_interval_minutes', (string) $statusSyncInterval);
|
||||
Flash::set('settings_success', $this->translator->get('settings.allegro.flash.import_settings_saved'));
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set(
|
||||
'settings_error',
|
||||
$this->translator->get('settings.allegro.flash.import_settings_save_failed') . ' ' . $exception->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/integrations/allegro?tab=settings');
|
||||
}
|
||||
|
||||
public function saveStatusMapping(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
$allegroStatusCode = strtolower(trim((string) $request->input('allegro_status_code', '')));
|
||||
$orderproStatusCode = strtolower(trim((string) $request->input('orderpro_status_code', '')));
|
||||
$allegroStatusName = trim((string) $request->input('allegro_status_name', ''));
|
||||
|
||||
if ($allegroStatusCode === '') {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.allegro_status_required'));
|
||||
return Response::redirect('/settings/integrations/allegro?tab=statuses');
|
||||
}
|
||||
|
||||
if ($orderproStatusCode === '') {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.orderpro_status_required'));
|
||||
return Response::redirect('/settings/integrations/allegro?tab=statuses');
|
||||
}
|
||||
|
||||
if (!$this->orderStatusCodeExists($orderproStatusCode)) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.orderpro_status_not_found'));
|
||||
return Response::redirect('/settings/integrations/allegro?tab=statuses');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->statusMappings->upsertMapping($allegroStatusCode, $allegroStatusName !== '' ? $allegroStatusName : null, $orderproStatusCode);
|
||||
Flash::set('settings_success', $this->translator->get('settings.allegro.statuses.flash.saved'));
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed') . ' ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/integrations/allegro?tab=statuses');
|
||||
}
|
||||
|
||||
public function saveStatusMappingsBulk(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
$codes = $request->input('allegro_status_code', []);
|
||||
$names = $request->input('allegro_status_name', []);
|
||||
$selectedOrderproCodes = $request->input('orderpro_status_code', []);
|
||||
if (!is_array($codes) || !is_array($names) || !is_array($selectedOrderproCodes)) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed'));
|
||||
return Response::redirect('/settings/integrations/allegro?tab=statuses');
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($codes as $index => $rawCode) {
|
||||
$allegroStatusCode = strtolower(trim((string) $rawCode));
|
||||
if ($allegroStatusCode === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$allegroStatusName = trim((string) ($names[$index] ?? ''));
|
||||
$orderproStatusCodeRaw = strtolower(trim((string) ($selectedOrderproCodes[$index] ?? '')));
|
||||
$orderproStatusCode = $orderproStatusCodeRaw !== '' ? $orderproStatusCodeRaw : null;
|
||||
|
||||
if ($orderproStatusCode !== null && !$this->orderStatusCodeExists($orderproStatusCode)) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.orderpro_status_not_found'));
|
||||
return Response::redirect('/settings/integrations/allegro?tab=statuses');
|
||||
}
|
||||
|
||||
$this->statusMappings->upsertMapping(
|
||||
$allegroStatusCode,
|
||||
$allegroStatusName !== '' ? $allegroStatusName : null,
|
||||
$orderproStatusCode
|
||||
);
|
||||
}
|
||||
|
||||
Flash::set('settings_success', $this->translator->get('settings.allegro.statuses.flash.saved_bulk'));
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed') . ' ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/integrations/allegro?tab=statuses');
|
||||
}
|
||||
|
||||
public function deleteStatusMapping(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
$mappingId = max(0, (int) $request->input('mapping_id', 0));
|
||||
if ($mappingId <= 0) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.mapping_not_found'));
|
||||
return Response::redirect('/settings/integrations/allegro?tab=statuses');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->statusMappings->deleteMappingById($mappingId);
|
||||
Flash::set('settings_success', $this->translator->get('settings.allegro.statuses.flash.deleted'));
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.delete_failed') . ' ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/integrations/allegro?tab=statuses');
|
||||
}
|
||||
|
||||
public function syncStatusesFromAllegro(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->statusDiscoveryService->discoverAndStoreStatuses(5, 100);
|
||||
Flash::set(
|
||||
'settings_success',
|
||||
$this->translator->get('settings.allegro.statuses.flash.sync_ok', [
|
||||
'discovered' => (string) ((int) ($result['discovered'] ?? 0)),
|
||||
'samples' => (string) ((int) ($result['samples'] ?? 0)),
|
||||
])
|
||||
);
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.sync_failed') . ' ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/integrations/allegro?tab=statuses');
|
||||
}
|
||||
|
||||
public function startOAuth(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
try {
|
||||
$credentials = $this->requireOAuthCredentials();
|
||||
$state = bin2hex(random_bytes(24));
|
||||
$_SESSION[self::OAUTH_STATE_SESSION_KEY] = $state;
|
||||
|
||||
$url = $this->oauthClient->buildAuthorizeUrl(
|
||||
(string) $credentials['environment'],
|
||||
(string) $credentials['client_id'],
|
||||
(string) $credentials['redirect_uri'],
|
||||
$state,
|
||||
self::OAUTH_SCOPES
|
||||
);
|
||||
|
||||
return Response::redirect($url);
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('settings_error', $exception->getMessage());
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
}
|
||||
|
||||
public function oauthCallback(Request $request): Response
|
||||
{
|
||||
$error = trim((string) $request->input('error', ''));
|
||||
if ($error !== '') {
|
||||
$description = trim((string) $request->input('error_description', ''));
|
||||
$message = $this->translator->get('settings.allegro.flash.oauth_failed');
|
||||
if ($description !== '') {
|
||||
$message .= ' ' . $description;
|
||||
}
|
||||
Flash::set('settings_error', $message);
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
$state = trim((string) $request->input('state', ''));
|
||||
$expectedState = trim((string) ($_SESSION[self::OAUTH_STATE_SESSION_KEY] ?? ''));
|
||||
unset($_SESSION[self::OAUTH_STATE_SESSION_KEY]);
|
||||
if ($state === '' || $expectedState === '' || !hash_equals($expectedState, $state)) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.flash.oauth_state_invalid'));
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
$authorizationCode = trim((string) $request->input('code', ''));
|
||||
if ($authorizationCode === '') {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.flash.oauth_code_missing'));
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
try {
|
||||
$credentials = $this->requireOAuthCredentials();
|
||||
$token = $this->oauthClient->exchangeAuthorizationCode(
|
||||
(string) $credentials['environment'],
|
||||
(string) $credentials['client_id'],
|
||||
(string) $credentials['client_secret'],
|
||||
(string) $credentials['redirect_uri'],
|
||||
$authorizationCode
|
||||
);
|
||||
|
||||
$expiresAt = null;
|
||||
if ((int) ($token['expires_in'] ?? 0) > 0) {
|
||||
$expiresAt = (new DateTimeImmutable('now'))
|
||||
->add(new DateInterval('PT' . (int) $token['expires_in'] . 'S'))
|
||||
->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$this->repository->saveTokens(
|
||||
(string) ($token['access_token'] ?? ''),
|
||||
(string) ($token['refresh_token'] ?? ''),
|
||||
(string) ($token['token_type'] ?? ''),
|
||||
(string) ($token['scope'] ?? ''),
|
||||
$expiresAt
|
||||
);
|
||||
|
||||
Flash::set('settings_success', $this->translator->get('settings.allegro.flash.oauth_connected'));
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.flash.oauth_failed') . ' ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
public function importSingleOrder(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
$checkoutFormId = trim((string) $request->input('checkout_form_id', ''));
|
||||
if ($checkoutFormId === '') {
|
||||
Flash::set('settings_error', $this->translator->get('settings.allegro.flash.checkout_form_id_required'));
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->orderImportService->importSingleOrder($checkoutFormId);
|
||||
$imageDiagnostics = is_array($result['image_diagnostics'] ?? null) ? $result['image_diagnostics'] : [];
|
||||
Flash::set(
|
||||
'settings_success',
|
||||
$this->translator->get('settings.allegro.flash.import_single_ok', [
|
||||
'source_order_id' => (string) ($result['source_order_id'] ?? $checkoutFormId),
|
||||
'local_id' => (string) ((int) ($result['order_id'] ?? 0)),
|
||||
'action' => !empty($result['created'])
|
||||
? $this->translator->get('settings.allegro.import_action.created')
|
||||
: $this->translator->get('settings.allegro.import_action.updated'),
|
||||
]) . ' '
|
||||
. $this->translator->get('settings.allegro.flash.import_single_media_summary', [
|
||||
'with_image' => (string) ((int) ($imageDiagnostics['with_image'] ?? 0)),
|
||||
'total_items' => (string) ((int) ($imageDiagnostics['total_items'] ?? 0)),
|
||||
'without_image' => (string) ((int) ($imageDiagnostics['without_image'] ?? 0)),
|
||||
])
|
||||
);
|
||||
|
||||
$warningDetails = $this->buildImportImageWarningMessage($imageDiagnostics);
|
||||
if ($warningDetails !== '') {
|
||||
Flash::set('settings_warning', $warningDetails);
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set(
|
||||
'settings_error',
|
||||
$this->translator->get('settings.allegro.flash.import_single_failed') . ' ' . $exception->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
private function defaultRedirectUri(): string
|
||||
{
|
||||
$base = trim($this->appUrl);
|
||||
if ($base === '') {
|
||||
$base = 'http://localhost:8000';
|
||||
}
|
||||
|
||||
return rtrim($base, '/') . '/settings/integrations/allegro/oauth/callback';
|
||||
}
|
||||
|
||||
private function validateCsrf(string $token): ?Response
|
||||
{
|
||||
if (Csrf::validate($token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired'));
|
||||
return Response::redirect('/settings/integrations/allegro');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function requireOAuthCredentials(): array
|
||||
{
|
||||
$credentials = $this->repository->getOAuthCredentials();
|
||||
if ($credentials === null) {
|
||||
throw new RuntimeException($this->translator->get('settings.allegro.flash.credentials_missing'));
|
||||
}
|
||||
|
||||
return $credentials;
|
||||
}
|
||||
|
||||
private function isValidHttpUrl(string $url): bool
|
||||
{
|
||||
$trimmed = trim($url);
|
||||
if ($trimmed === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter_var($trimmed, FILTER_VALIDATE_URL) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$scheme = strtolower((string) parse_url($trimmed, PHP_URL_SCHEME));
|
||||
return $scheme === 'http' || $scheme === 'https';
|
||||
}
|
||||
|
||||
private function isValidDate(string $value): bool
|
||||
{
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$date = DateTimeImmutable::createFromFormat('Y-m-d', $value);
|
||||
return $date instanceof DateTimeImmutable && $date->format('Y-m-d') === $value;
|
||||
}
|
||||
|
||||
private function orderStatusCodeExists(string $code): bool
|
||||
{
|
||||
$needle = strtolower(trim($code));
|
||||
if ($needle === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->orderStatuses->listStatuses() as $row) {
|
||||
$statusCode = strtolower(trim((string) ($row['code'] ?? '')));
|
||||
if ($statusCode === $needle) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $imageDiagnostics
|
||||
*/
|
||||
private function buildImportImageWarningMessage(array $imageDiagnostics): string
|
||||
{
|
||||
$withoutImage = (int) ($imageDiagnostics['without_image'] ?? 0);
|
||||
if ($withoutImage <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$reasonCountsRaw = $imageDiagnostics['reason_counts'] ?? [];
|
||||
if (!is_array($reasonCountsRaw) || $reasonCountsRaw === []) {
|
||||
return $this->translator->get('settings.allegro.flash.import_single_media_warning_generic', [
|
||||
'without_image' => (string) $withoutImage,
|
||||
]);
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
foreach ($reasonCountsRaw as $reason => $countRaw) {
|
||||
$count = (int) $countRaw;
|
||||
if ($count <= 0) {
|
||||
continue;
|
||||
}
|
||||
$parts[] = $this->reasonLabel((string) $reason) . ': ' . $count;
|
||||
}
|
||||
|
||||
if ($parts === []) {
|
||||
return $this->translator->get('settings.allegro.flash.import_single_media_warning_generic', [
|
||||
'without_image' => (string) $withoutImage,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->translator->get('settings.allegro.flash.import_single_media_warning', [
|
||||
'without_image' => (string) $withoutImage,
|
||||
'reasons' => implode(', ', $parts),
|
||||
]);
|
||||
}
|
||||
|
||||
private function reasonLabel(string $reasonCode): string
|
||||
{
|
||||
return match ($reasonCode) {
|
||||
'missing_offer_id' => 'brak ID oferty',
|
||||
'missing_in_checkout_form' => 'brak obrazka w checkout form',
|
||||
'missing_in_offer_api' => 'brak obrazka w API oferty',
|
||||
'offer_api_access_denied_403' => 'brak uprawnien API ofert (403)',
|
||||
'offer_api_unauthorized_401' => 'token nieautoryzowany dla API ofert (401)',
|
||||
'offer_api_not_found_404' => 'oferta nie znaleziona (404)',
|
||||
'offer_api_request_failed' => 'blad zapytania do API oferty',
|
||||
default => str_starts_with($reasonCode, 'offer_api_http_')
|
||||
? 'blad API oferty (' . str_replace('offer_api_http_', '', $reasonCode) . ')'
|
||||
: $reasonCode,
|
||||
};
|
||||
}
|
||||
|
||||
private function currentImportIntervalSeconds(): int
|
||||
{
|
||||
$schedule = $this->findImportSchedule();
|
||||
$value = (int) ($schedule['interval_seconds'] ?? self::ORDERS_IMPORT_DEFAULT_INTERVAL_SECONDS);
|
||||
return max(60, min(86400, $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function findImportSchedule(): array
|
||||
{
|
||||
try {
|
||||
$schedules = $this->cronRepository->listSchedules();
|
||||
} catch (Throwable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($schedules as $schedule) {
|
||||
if (!is_array($schedule)) {
|
||||
continue;
|
||||
}
|
||||
if ((string) ($schedule['job_type'] ?? '') !== self::ORDERS_IMPORT_JOB_TYPE) {
|
||||
continue;
|
||||
}
|
||||
return $schedule;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function findStatusSyncSchedule(): array
|
||||
{
|
||||
try {
|
||||
$schedules = $this->cronRepository->listSchedules();
|
||||
} catch (Throwable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($schedules as $schedule) {
|
||||
if (!is_array($schedule)) {
|
||||
continue;
|
||||
}
|
||||
if ((string) ($schedule['job_type'] ?? '') !== self::STATUS_SYNC_JOB_TYPE) {
|
||||
continue;
|
||||
}
|
||||
return $schedule;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function currentStatusSyncDirection(): string
|
||||
{
|
||||
$value = trim($this->cronRepository->getStringSetting(
|
||||
'allegro_status_sync_direction',
|
||||
self::STATUS_SYNC_DIRECTION_ALLEGRO_TO_ORDERPRO
|
||||
));
|
||||
|
||||
if (!in_array($value, $this->allowedStatusSyncDirections(), true)) {
|
||||
return self::STATUS_SYNC_DIRECTION_ALLEGRO_TO_ORDERPRO;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function currentStatusSyncIntervalMinutes(): int
|
||||
{
|
||||
return $this->cronRepository->getIntSetting(
|
||||
'allegro_status_sync_interval_minutes',
|
||||
self::STATUS_SYNC_DEFAULT_INTERVAL_MINUTES,
|
||||
1,
|
||||
1440
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function allowedStatusSyncDirections(): array
|
||||
{
|
||||
return [
|
||||
self::STATUS_SYNC_DIRECTION_ALLEGRO_TO_ORDERPRO,
|
||||
self::STATUS_SYNC_DIRECTION_ORDERPRO_TO_ALLEGRO,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user