resolveTab((string) $request->input('tab', 'integration')); $html = $this->template->render('settings/erli', [ 'title' => $this->translator->get('settings.erli.title'), 'activeMenu' => 'settings', 'activeSettings' => 'integrations', 'user' => $this->auth->user(), 'csrfToken' => Csrf::token(), 'activeTab' => $activeTab, 'settings' => $this->repository->getSettings(), 'ordersImportIntervalMinutes' => $this->currentImportIntervalMinutes(), 'statusSyncDirection' => $this->currentStatusSyncDirection(), 'statusSyncIntervalMinutes' => $this->currentStatusSyncIntervalMinutes(), 'orderproStatuses' => $this->orderStatuses->listStatuses(), 'erliStatusMappings' => $this->statusMappings->listAll(), 'erliPullStatusMappings' => $this->pullStatusMappings->listAll(), 'errorMessage' => (string) Flash::get('settings_error', ''), 'successMessage' => (string) Flash::get('settings_success', ''), 'testMessage' => (string) Flash::get('erli_test', ''), 'importMessage' => (string) Flash::get('erli_import', ''), ], 'layouts/app'); return Response::html($html); } public function save(Request $request): Response { $redirectTo = $this->resolveRedirect($request); if (!Csrf::validate((string) $request->input('_token', ''))) { Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); return Response::redirect($redirectTo); } try { $this->repository->saveSettings([ 'account_label' => (string) $request->input('account_label', ''), 'api_key' => (string) $request->input('api_key', ''), 'is_active' => $request->input('is_active', ''), 'orders_fetch_enabled' => $request->input('orders_fetch_enabled', ''), 'orders_fetch_start_date' => $this->validateStartDate((string) $request->input('orders_fetch_start_date', '')), ]); $this->upsertImportSchedule($request); $this->saveStatusSyncSettings($request); Flash::set('settings_success', $this->translator->get('settings.erli.flash.saved')); } catch (Throwable $exception) { Flash::set( 'settings_error', $this->translator->get('settings.erli.flash.save_failed') . ' ' . $exception->getMessage() ); } return Response::redirect($redirectTo); } public function savePullStatusMappings(Request $request): Response { $redirectTo = $this->resolveRedirect($request); if (!Csrf::validate((string) $request->input('_token', ''))) { Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); return Response::redirect($redirectTo); } $erliCodes = $request->input('erli_status_code', []); $erliNames = $request->input('erli_status_name', []); $orderproCodes = $request->input('orderpro_status_code', []); if (!is_array($erliCodes) || !is_array($erliNames) || !is_array($orderproCodes)) { Flash::set('settings_error', $this->translator->get('settings.erli.statuses.flash.save_failed')); return Response::redirect($redirectTo); } $mappings = []; foreach ($erliCodes as $index => $rawErliCode) { $erliCode = strtolower(trim((string) $rawErliCode)); if ($erliCode === '') { continue; } $mappings[] = [ 'erli_status_code' => $erliCode, 'erli_status_name' => trim((string) ($erliNames[$index] ?? '')), 'orderpro_status_code' => strtolower(trim((string) ($orderproCodes[$index] ?? ''))), ]; } try { $this->pullStatusMappings->replaceAll($mappings); Flash::set('settings_success', $this->translator->get('settings.erli.statuses.flash.saved_pull')); } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.erli.statuses.flash.save_failed') . ' ' . $exception->getMessage()); } return Response::redirect($redirectTo); } public function savePushStatusMappings(Request $request): Response { $redirectTo = $this->resolveRedirect($request); if (!Csrf::validate((string) $request->input('_token', ''))) { Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); return Response::redirect($redirectTo); } $erliCodes = $request->input('erli_status_code', []); $erliNames = $request->input('erli_status_name', []); $orderproCodes = $request->input('orderpro_status_code', []); if (!is_array($erliCodes) || !is_array($erliNames) || !is_array($orderproCodes)) { Flash::set('settings_error', $this->translator->get('settings.erli.statuses.flash.save_failed')); return Response::redirect($redirectTo); } $mappings = []; foreach ($erliCodes as $index => $rawErliCode) { $erliCode = trim((string) $rawErliCode); if ($erliCode === '') { continue; } $mappings[] = [ 'erli_status_code' => $erliCode, 'erli_status_name' => trim((string) ($erliNames[$index] ?? '')), 'orderpro_status_code' => strtolower(trim((string) ($orderproCodes[$index] ?? ''))), ]; } try { $this->statusMappings->replaceAll($mappings); Flash::set('settings_success', $this->translator->get('settings.erli.statuses.flash.saved_push')); } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.erli.statuses.flash.save_failed') . ' ' . $exception->getMessage()); } return Response::redirect($redirectTo); } public function importNow(Request $request): Response { $redirectTo = $this->resolveRedirect($request); if (!Csrf::validate((string) $request->input('_token', ''))) { Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); return Response::redirect($redirectTo); } try { $result = $this->ordersSyncService->sync([ 'ignore_orders_fetch_enabled' => true, 'max_messages' => 100, ]); Flash::set('erli_import', $this->formatImportResult($result)); } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.erli.flash.import_failed') . ' ' . $exception->getMessage()); } return Response::redirect($redirectTo); } public function test(Request $request): Response { $redirectTo = $this->resolveRedirect($request); if (!Csrf::validate((string) $request->input('_token', ''))) { Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); return Response::redirect($redirectTo); } try { $credentials = $this->repository->getCredentials(); if ($credentials === null) { throw new IntegrationConfigException('Najpierw zapisz kompletna i aktywna konfiguracje Erli.'); } $result = $this->apiClient->testConnection($credentials); $this->integrations->updateTestResult( $credentials['integration_id'], $result['ok'] ? 'ok' : 'fail', (int) $result['http_code'], (string) $result['message'] ); if ($result['ok']) { Flash::set('erli_test', $this->translator->get('settings.erli.flash.test_success')); } else { Flash::set('settings_error', $this->translator->get('settings.erli.flash.test_failed') . ' ' . $result['message']); } } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.erli.flash.test_failed') . ' ' . $exception->getMessage()); } return Response::redirect($redirectTo); } private function resolveRedirect(Request $request): string { return RedirectPathResolver::resolve( (string) $request->input('return_to', '/settings/integrations/erli'), ['/settings/integrations'], '/settings/integrations/erli' ); } private function resolveTab(string $tab): string { $normalized = trim($tab); if (in_array($normalized, ['integration', 'statuses', 'settings'], true)) { return $normalized; } return 'integration'; } private function validateStartDate(string $value): string { $trimmed = trim($value); if ($trimmed === '') { return ''; } if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $trimmed) !== 1) { throw new IntegrationConfigException($this->translator->get('settings.erli.validation.orders_fetch_start_date_invalid')); } return $trimmed; } private function upsertImportSchedule(Request $request): void { $minutes = max(1, min(1440, (int) $request->input('orders_import_interval_minutes', 5))); $enabled = (string) $request->input('orders_fetch_enabled', '') === '1'; $this->cronRepository->upsertSchedule( self::ORDERS_IMPORT_JOB_TYPE, $minutes * 60, self::ORDERS_IMPORT_DEFAULT_PRIORITY, self::ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS, null, $enabled ); } private function saveStatusSyncSettings(Request $request): void { $direction = trim((string) $request->input('status_sync_direction', self::STATUS_SYNC_DIRECTION_PULL)); if (!in_array($direction, [self::STATUS_SYNC_DIRECTION_PULL, self::STATUS_SYNC_DIRECTION_PUSH], true)) { throw new IntegrationConfigException($this->translator->get('settings.erli.validation.status_sync_direction_invalid')); } $minutes = max(1, min(1440, (int) $request->input('status_sync_interval_minutes', 15))); $enabled = (string) $request->input('is_active', '') === '1'; $this->cronRepository->upsertSetting('erli_status_sync_direction', $direction); $this->cronRepository->upsertSetting('erli_status_sync_interval_minutes', (string) $minutes); $this->cronRepository->upsertSchedule( self::STATUS_SYNC_JOB_TYPE, $minutes * 60, self::STATUS_SYNC_DEFAULT_PRIORITY, self::STATUS_SYNC_DEFAULT_MAX_ATTEMPTS, null, $enabled ); } private function currentImportIntervalMinutes(): int { foreach ($this->cronRepository->listSchedules() as $schedule) { if ((string) ($schedule['job_type'] ?? '') !== self::ORDERS_IMPORT_JOB_TYPE) { continue; } $seconds = (int) ($schedule['interval_seconds'] ?? self::ORDERS_IMPORT_DEFAULT_INTERVAL_SECONDS); return max(1, min(1440, (int) floor(max(60, $seconds) / 60))); } return 5; } private function currentStatusSyncDirection(): string { $direction = trim($this->cronRepository->getStringSetting('erli_status_sync_direction', self::STATUS_SYNC_DIRECTION_PULL)); return in_array($direction, [self::STATUS_SYNC_DIRECTION_PULL, self::STATUS_SYNC_DIRECTION_PUSH], true) ? $direction : self::STATUS_SYNC_DIRECTION_PULL; } private function currentStatusSyncIntervalMinutes(): int { foreach ($this->cronRepository->listSchedules() as $schedule) { if ((string) ($schedule['job_type'] ?? '') !== self::STATUS_SYNC_JOB_TYPE) { continue; } $seconds = (int) ($schedule['interval_seconds'] ?? self::STATUS_SYNC_DEFAULT_INTERVAL_SECONDS); return max(1, min(1440, (int) floor(max(60, $seconds) / 60))); } return $this->cronRepository->getIntSetting('erli_status_sync_interval_minutes', 15, 1, 1440); } /** * @param array $result */ private function formatImportResult(array $result): string { return strtr($this->translator->get('settings.erli.flash.import_success'), [ ':processed' => (string) (int) ($result['processed'] ?? 0), ':created' => (string) (int) ($result['imported_created'] ?? 0), ':updated' => (string) (int) ($result['imported_updated'] ?? 0), ':failed' => (string) (int) ($result['failed'] ?? 0), ':skipped' => (string) (int) ($result['skipped'] ?? 0), ':ack' => !empty($result['acknowledged']) ? 'tak' : 'nie', ]); } }