feat(129): erli status mapping sync
Phase 129 complete: - Add Erli pull/push status mapping tables, seeds and repositories - Wire Erli status sync cron for inbox pull and manual-only push - Add tabbed Erli settings UI, tests and documentation Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -863,6 +863,12 @@ return [
|
||||
'erli' => [
|
||||
'title' => 'Integracja Erli',
|
||||
'description' => 'Konfiguracja globalnego polaczenia z marketplace Erli.',
|
||||
'tabs' => [
|
||||
'label' => 'Zakladki integracji Erli',
|
||||
'integration' => 'Integracja',
|
||||
'statuses' => 'Statusy',
|
||||
'settings' => 'Ustawienia',
|
||||
],
|
||||
'config' => [
|
||||
'title' => 'Konfiguracja API',
|
||||
],
|
||||
@@ -882,6 +888,10 @@ return [
|
||||
'orders_fetch_enabled' => 'Wlacz automatyczny import zamowien',
|
||||
'orders_fetch_start_date' => 'Data startu importu',
|
||||
'orders_import_interval_minutes' => 'Interwal importu (minuty)',
|
||||
'status_sync_direction' => 'Kierunek synchronizacji statusow',
|
||||
'status_sync_direction_pull' => 'Erli -> orderPRO',
|
||||
'status_sync_direction_push' => 'orderPRO -> Erli',
|
||||
'status_sync_interval_minutes' => 'Interwal synchronizacji statusow (minuty)',
|
||||
],
|
||||
'api_key' => [
|
||||
'saved' => 'Klucz API jest zapisany. Pozostaw pole puste, aby nie zmieniac.',
|
||||
@@ -891,6 +901,29 @@ return [
|
||||
'account_label' => 'Opcjonalna nazwa widoczna w hubie integracji.',
|
||||
'orders_fetch_start_date' => 'Opcjonalnie pominie zdarzenia starsze niz podana data, jesli payload Erli zawiera date zamowienia.',
|
||||
'orders_import_interval_minutes' => 'Dotyczy zadania cron `erli_orders_import`. Zakres: 1-1440 minut.',
|
||||
'status_sync_direction' => 'Pull pobiera statusy przez inbox. Push wysyla reczne zmiany statusu z orderPRO do Erli.',
|
||||
'status_sync_interval_minutes' => 'Dotyczy zadania cron `erli_status_sync`. Zakres: 1-1440 minut.',
|
||||
],
|
||||
'statuses' => [
|
||||
'pull_title' => 'Mapowanie przy imporcie (Erli -> orderPRO)',
|
||||
'pull_description' => 'Przypisz surowe statusy Erli do statusow orderPRO. Nieznane statusy z inboxa beda dopisywane automatycznie po imporcie.',
|
||||
'push_title' => 'Mapowanie przy wysylce (orderPRO -> Erli)',
|
||||
'push_description' => 'Przypisz statusy orderPRO do statusow Erli uzywanych przy recznej zmianie statusu i cron push.',
|
||||
'empty_pull' => 'Brak statusow Erli do mapowania. Uruchom migracje lub import, aby zasilic liste.',
|
||||
'empty_push' => 'Brak statusow Erli do mapowania. Uruchom migracje, aby zasilic liste.',
|
||||
'fields' => [
|
||||
'erli_status' => 'Status Erli',
|
||||
'orderpro_status' => 'Status orderPRO',
|
||||
],
|
||||
'actions' => [
|
||||
'save_pull' => 'Zapisz mapowanie importu',
|
||||
'save_push' => 'Zapisz mapowanie wysylki',
|
||||
],
|
||||
'flash' => [
|
||||
'saved_pull' => 'Mapowanie importu statusow Erli zostalo zapisane.',
|
||||
'saved_push' => 'Mapowanie wysylki statusow Erli zostalo zapisane.',
|
||||
'save_failed' => 'Nie udalo sie zapisac mapowan statusow Erli.',
|
||||
],
|
||||
],
|
||||
'status' => [
|
||||
'secret' => 'Sekret API',
|
||||
@@ -914,6 +947,7 @@ return [
|
||||
],
|
||||
'validation' => [
|
||||
'orders_fetch_start_date_invalid' => 'Data startu importu musi miec format RRRR-MM-DD.',
|
||||
'status_sync_direction_invalid' => 'Wybierz poprawny kierunek synchronizacji statusow Erli.',
|
||||
],
|
||||
],
|
||||
'inpost' => [
|
||||
|
||||
@@ -10,6 +10,12 @@ $lastTestHttpCode = $settings['last_test_http_code'] ?? null;
|
||||
$ordersFetchEnabled = (bool) ($settings['orders_fetch_enabled'] ?? false);
|
||||
$ordersFetchStartDate = trim((string) ($settings['orders_fetch_start_date'] ?? ''));
|
||||
$ordersImportIntervalMinutes = (int) ($ordersImportIntervalMinutes ?? 5);
|
||||
$statusSyncDirection = (string) ($statusSyncDirection ?? 'erli_to_orderpro');
|
||||
$statusSyncIntervalMinutes = (int) ($statusSyncIntervalMinutes ?? 15);
|
||||
$orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : [];
|
||||
$erliStatusMappings = is_array($erliStatusMappings ?? null) ? $erliStatusMappings : [];
|
||||
$erliPullStatusMappings = is_array($erliPullStatusMappings ?? null) ? $erliPullStatusMappings : [];
|
||||
$activeTab = (string) ($activeTab ?? 'integration');
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
@@ -34,18 +40,33 @@ $ordersImportIntervalMinutes = (int) ($ordersImportIntervalMinutes ?? 5);
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title"><?= $e($t('settings.erli.config.title')) ?></h3>
|
||||
<nav class="content-tabs-nav" aria-label="<?= $e($t('settings.erli.tabs.label')) ?>">
|
||||
<button type="button" class="content-tab-btn<?= $activeTab === 'integration' ? ' is-active' : '' ?>" data-tab-target="erli-tab-integration">
|
||||
<?= $e($t('settings.erli.tabs.integration')) ?>
|
||||
</button>
|
||||
<button type="button" class="content-tab-btn<?= $activeTab === 'statuses' ? ' is-active' : '' ?>" data-tab-target="erli-tab-statuses">
|
||||
<?= $e($t('settings.erli.tabs.statuses')) ?>
|
||||
</button>
|
||||
<button type="button" class="content-tab-btn<?= $activeTab === 'settings' ? ' is-active' : '' ?>" data-tab-target="erli-tab-settings">
|
||||
<?= $e($t('settings.erli.tabs.settings')) ?>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="muted mt-12">
|
||||
<?= $e($t('settings.erli.status.secret')) ?>:
|
||||
<strong><?= $e($hasApiKey ? $t('settings.erli.status.saved') : $t('settings.erli.status.missing')) ?></strong>
|
||||
|
|
||||
<?= $e($t('settings.erli.status.active')) ?>:
|
||||
<strong><?= $e($isActive ? $t('settings.integrations_hub.active.yes') : $t('settings.integrations_hub.active.no')) ?></strong>
|
||||
</div>
|
||||
<div class="content-tab-panel<?= $activeTab === 'integration' ? ' is-active' : '' ?>" data-tab-panel="erli-tab-integration">
|
||||
<section class="mt-16">
|
||||
<h3 class="section-title"><?= $e($t('settings.erli.config.title')) ?></h3>
|
||||
|
||||
<form class="statuses-form mt-16" action="/settings/integrations/erli/save" method="post" novalidate>
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<div class="muted mt-12">
|
||||
<?= $e($t('settings.erli.status.secret')) ?>:
|
||||
<strong><?= $e($hasApiKey ? $t('settings.erli.status.saved') : $t('settings.erli.status.missing')) ?></strong>
|
||||
|
|
||||
<?= $e($t('settings.erli.status.active')) ?>:
|
||||
<strong><?= $e($isActive ? $t('settings.integrations_hub.active.yes') : $t('settings.integrations_hub.active.no')) ?></strong>
|
||||
</div>
|
||||
|
||||
<form class="statuses-form mt-16" action="/settings/integrations/erli/save" method="post" novalidate>
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="return_to" value="/settings/integrations/erli?tab=integration">
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.erli.fields.account_label')) ?></span>
|
||||
@@ -85,30 +106,182 @@ $ordersImportIntervalMinutes = (int) ($ordersImportIntervalMinutes ?? 5);
|
||||
<span class="muted"><?= $e($t('settings.erli.hints.orders_import_interval_minutes')) ?></span>
|
||||
</label>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.erli.actions.save')) ?></button>
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.erli.fields.status_sync_direction')) ?></span>
|
||||
<select class="form-control" name="status_sync_direction">
|
||||
<option value="erli_to_orderpro"<?= $statusSyncDirection === 'erli_to_orderpro' ? ' selected' : '' ?>>
|
||||
<?= $e($t('settings.erli.fields.status_sync_direction_pull')) ?>
|
||||
</option>
|
||||
<option value="orderpro_to_erli"<?= $statusSyncDirection === 'orderpro_to_erli' ? ' selected' : '' ?>>
|
||||
<?= $e($t('settings.erli.fields.status_sync_direction_push')) ?>
|
||||
</option>
|
||||
</select>
|
||||
<span class="muted"><?= $e($t('settings.erli.hints.status_sync_direction')) ?></span>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.erli.fields.status_sync_interval_minutes')) ?></span>
|
||||
<input class="form-control" type="number" min="1" max="1440" name="status_sync_interval_minutes" value="<?= $e((string) $statusSyncIntervalMinutes) ?>">
|
||||
<span class="muted"><?= $e($t('settings.erli.hints.status_sync_interval_minutes')) ?></span>
|
||||
</label>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.erli.actions.save')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="content-tab-panel<?= $activeTab === 'statuses' ? ' is-active' : '' ?>" data-tab-panel="erli-tab-statuses">
|
||||
<section class="mt-16">
|
||||
<h3 class="section-title"><?= $e($t('settings.erli.statuses.pull_title')) ?></h3>
|
||||
<p class="muted mt-12"><?= $e($t('settings.erli.statuses.pull_description')) ?></p>
|
||||
|
||||
<form action="/settings/integrations/erli/statuses/save-pull" method="post" class="mt-12">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="return_to" value="/settings/integrations/erli?tab=statuses">
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $e($t('settings.erli.statuses.fields.erli_status')) ?></th>
|
||||
<th><?= $e($t('settings.erli.statuses.fields.orderpro_status')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($erliPullStatusMappings === []): ?>
|
||||
<tr>
|
||||
<td colspan="2" class="muted"><?= $e($t('settings.erli.statuses.empty_pull')) ?></td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($erliPullStatusMappings as $mapping): ?>
|
||||
<?php
|
||||
$erliCode = strtolower(trim((string) ($mapping['erli_status_code'] ?? '')));
|
||||
if ($erliCode === '') continue;
|
||||
$erliName = trim((string) ($mapping['erli_status_name'] ?? ''));
|
||||
$selectedOrderproCode = strtolower(trim((string) ($mapping['orderpro_status_code'] ?? '')));
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= $e($erliName !== '' ? $erliName : $erliCode) ?> <code class="muted"><?= $e($erliCode) ?></code>
|
||||
<input type="hidden" name="erli_status_code[]" value="<?= $e($erliCode) ?>">
|
||||
<input type="hidden" name="erli_status_name[]" value="<?= $e($erliName) ?>">
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-control" name="orderpro_status_code[]">
|
||||
<option value=""><?= $e($t('settings.order_statuses.fields.no_mapping')) ?></option>
|
||||
<?php foreach ($orderproStatuses as $status): ?>
|
||||
<?php
|
||||
$opCode = strtolower(trim((string) ($status['code'] ?? '')));
|
||||
if ($opCode === '') continue;
|
||||
$opName = (string) ($status['name'] ?? $opCode);
|
||||
?>
|
||||
<option value="<?= $e($opCode) ?>"<?= $selectedOrderproCode === $opCode ? ' selected' : '' ?>>
|
||||
<?= $e($opName) ?> (<?= $e($opCode) ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if ($erliPullStatusMappings !== []): ?>
|
||||
<div class="form-actions mt-12">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.erli.statuses.actions.save_pull')) ?></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<section class="mt-16">
|
||||
<h3 class="section-title"><?= $e($t('settings.erli.statuses.push_title')) ?></h3>
|
||||
<p class="muted mt-12"><?= $e($t('settings.erli.statuses.push_description')) ?></p>
|
||||
|
||||
<form action="/settings/integrations/erli/statuses/save-push" method="post" class="mt-12">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="return_to" value="/settings/integrations/erli?tab=statuses">
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $e($t('settings.erli.statuses.fields.erli_status')) ?></th>
|
||||
<th><?= $e($t('settings.erli.statuses.fields.orderpro_status')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($erliStatusMappings === []): ?>
|
||||
<tr>
|
||||
<td colspan="2" class="muted"><?= $e($t('settings.erli.statuses.empty_push')) ?></td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($erliStatusMappings as $mapping): ?>
|
||||
<?php
|
||||
$erliCode = trim((string) ($mapping['erli_status_code'] ?? ''));
|
||||
if ($erliCode === '') continue;
|
||||
$erliName = trim((string) ($mapping['erli_status_name'] ?? ''));
|
||||
$selectedOrderproCode = strtolower(trim((string) ($mapping['orderpro_status_code'] ?? '')));
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= $e($erliName !== '' ? $erliName : $erliCode) ?> <code class="muted"><?= $e($erliCode) ?></code>
|
||||
<input type="hidden" name="erli_status_code[]" value="<?= $e($erliCode) ?>">
|
||||
<input type="hidden" name="erli_status_name[]" value="<?= $e($erliName) ?>">
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-control" name="orderpro_status_code[]">
|
||||
<option value=""><?= $e($t('settings.order_statuses.fields.no_mapping')) ?></option>
|
||||
<?php foreach ($orderproStatuses as $status): ?>
|
||||
<?php
|
||||
$opCode = strtolower(trim((string) ($status['code'] ?? '')));
|
||||
if ($opCode === '') continue;
|
||||
$opName = (string) ($status['name'] ?? $opCode);
|
||||
?>
|
||||
<option value="<?= $e($opCode) ?>"<?= $selectedOrderproCode === $opCode ? ' selected' : '' ?>>
|
||||
<?= $e($opName) ?> (<?= $e($opCode) ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if ($erliStatusMappings !== []): ?>
|
||||
<div class="form-actions mt-12">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.erli.statuses.actions.save_push')) ?></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content-tab-panel<?= $activeTab === 'settings' ? ' is-active' : '' ?>" data-tab-panel="erli-tab-settings">
|
||||
<section class="mt-16">
|
||||
<h3 class="section-title"><?= $e($t('settings.erli.import.title')) ?></h3>
|
||||
<p class="muted mt-12"><?= $e($t('settings.erli.import.description')) ?></p>
|
||||
|
||||
<form class="statuses-form mt-16" action="/settings/integrations/erli/import" method="post">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="return_to" value="/settings/integrations/erli?tab=settings">
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn--secondary"><?= $e($t('settings.erli.actions.import_now')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<section class="mt-16">
|
||||
<h3 class="section-title"><?= $e($t('settings.erli.test.title')) ?></h3>
|
||||
<p class="muted mt-12"><?= $e($t('settings.erli.test.description')) ?></p>
|
||||
|
||||
<form class="statuses-form mt-16" action="/settings/integrations/erli/test" method="post">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="return_to" value="/settings/integrations/erli?tab=settings">
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn--secondary"><?= $e($t('settings.erli.actions.test')) ?></button>
|
||||
</div>
|
||||
@@ -128,3 +301,40 @@ $ordersImportIntervalMinutes = (int) ($ordersImportIntervalMinutes ?? 5);
|
||||
?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var tabs = document.querySelectorAll('[data-tab-target]');
|
||||
var panels = document.querySelectorAll('[data-tab-panel]');
|
||||
if (tabs.length === 0 || panels.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tabNameMap = {
|
||||
'erli-tab-integration': 'integration',
|
||||
'erli-tab-statuses': 'statuses',
|
||||
'erli-tab-settings': 'settings'
|
||||
};
|
||||
|
||||
tabs.forEach(function (tab) {
|
||||
tab.addEventListener('click', function () {
|
||||
var target = tab.getAttribute('data-tab-target');
|
||||
var tabName = tabNameMap[target] || 'integration';
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set('tab', tabName);
|
||||
window.history.replaceState(null, '', url.toString());
|
||||
|
||||
tabs.forEach(function (node) { node.classList.remove('is-active'); });
|
||||
panels.forEach(function (panel) { panel.classList.remove('is-active'); });
|
||||
tab.classList.add('is-active');
|
||||
|
||||
var panel = document.querySelector('[data-tab-panel="' + target + '"]');
|
||||
if (panel) {
|
||||
panel.classList.add('is-active');
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user