feat(128): polkurier shipment service + tracking + UI prepare

PolkurierApiClient rozszerzony do pelnego kontraktu (7 metod):
createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers/
getInpostParcelMachines/getCourierPoints. Wspolny call() parsuje
envelope {status, response}. Kontrakt zweryfikowany na oficjalnej
dokumentacji PDF v1.11.

PolkurierShipmentService (implements ShipmentProviderInterface)
orchestruje pelen flow: normalizeShipmentType (lowercase), split
ulicy, build recipient/sender/pickup, COD z bank account z
company_settings, extractOrderNumber/extractTrackingNumber
priorytetujace SDK Order entity (number, waybills[0].number).

PolkurierTrackingService (implements ShipmentTrackingInterface)
mapuje statusy O/P/A/WP/D/Z/W przez delivery_status_mappings.

UI panel polkurier w prepare.php z dynamiczna lista uslug z
available_carriers. Bez dedykowanego selektora punktu — operator
wpisuje receiver_point_id w istniejace pole w sekcji Adres odbiorcy.

Migracja 20260514_000115 seedujaca 7 wpisow delivery_status_mappings
z oficjalnej tabeli ORDER_STATUS (O/P/A/WP/D/Z/W).

Live test #114/#115 zakonczony sukcesem po 4 iteracjach
(ReferenceError -> uppercase shipmenttype -> orderno parsing ->
A4/A6 etykieta). Rozmiar etykiety A4/A6 sterowany w panelu klienta
polkurier.pl, NIE przez API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 12:56:36 +02:00
parent 3443879f59
commit c78ac335ee
19 changed files with 5011 additions and 102 deletions

View File

@@ -6,6 +6,7 @@ $prefs = is_array($preferences ?? null) ? $preferences : [];
$comp = is_array($company ?? null) ? $company : [];
$services = is_array($deliveryServices ?? null) ? $deliveryServices : [];
$apaczkaSvcList = is_array($apaczkaServices ?? null) ? $apaczkaServices : [];
$polkurierSvcList = is_array($polkurierServices ?? null) ? $polkurierServices : [];
$packages = is_array($existingPackages ?? null) ? $existingPackages : [];
$pendingPrintIds = is_array($pendingPrintPackageIds ?? null) ? $pendingPrintPackageIds : [];
$servicesError = (string) ($deliveryServicesError ?? '');
@@ -101,6 +102,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
<option value="allegro"<?= $preselectedCarrier === 'allegro' ? ' selected' : '' ?>>Allegro</option>
<option value="inpost"<?= $preselectedCarrier === 'inpost' ? ' selected' : '' ?>>InPost</option>
<option value="apaczka"<?= $preselectedCarrier === 'apaczka' ? ' selected' : '' ?>>Apaczka</option>
<option value="polkurier"<?= $preselectedCarrier === 'polkurier' ? ' selected' : '' ?>>polkurier</option>
</select>
<?php if ($deliveryMethodName !== ''): ?>
<div class="muted mt-4" style="font-size:12px">Metoda z zamowienia: <strong><?= $e($deliveryMethodName) ?></strong><?php if ($mappedServiceName !== ''): ?> &rarr; <?= $e($mappedCarrier === 'inpost' ? 'InPost' : ($mappedCarrier === 'apaczka' ? 'Apaczka' : 'Allegro')) ?>: <?= $e($mappedServiceName) ?><?php endif; ?></div>
@@ -209,12 +211,45 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
</div>
</div>
<div id="shipment-polkurier-panel" style="<?= $preselectedCarrier !== 'polkurier' ? 'display:none' : '' ?>">
<?php if ($polkurierSvcList === []): ?>
<div class="muted">Brak uslug polkurier (sprawdz konfiguracje w Ustawienia &rarr; Integracje &rarr; polkurier).</div>
<?php else: ?>
<select class="form-control" id="shipment-polkurier-select">
<option value="">-- Wybierz usluge polkurier --</option>
<?php foreach ($polkurierSvcList as $pSvc): ?>
<?php
if (!is_array($pSvc)) {
continue;
}
$pSvcId = trim((string) ($pSvc['id'] ?? ''));
$pSvcName = trim((string) ($pSvc['name'] ?? $pSvcId));
if ($pSvcId === '') {
continue;
}
?>
<option
value="<?= $e($pSvcId) ?>"
data-carrier-id="<?= $e($pSvcId) ?>">
<?= $e($pSvcName) ?>
</option>
<?php endforeach; ?>
</select>
<div class="muted mt-4" style="font-size:12px">Dla uslug paczkomatowych wpisz ID punktu w pole "Punkt odbioru" w sekcji Adres odbiorcy ponizej (np. <code>POP-RZE54</code>).</div>
<?php endif; ?>
</div>
<div id="shipment-empty-panel" class="muted" style="<?= $preselectedCarrier !== '' ? 'display:none' : '' ?>">Wybierz przewoznika</div>
</div>
<input type="hidden" name="credentials_id" id="shipment-credentials-id" value="">
<input type="hidden" name="carrier_id" id="shipment-carrier-id" value="">
<input type="hidden" name="provider_code" id="shipment-provider-code" value="<?= $e($preselectedCarrier === 'apaczka' ? 'apaczka' : 'allegro_wza') ?>">
<input type="hidden" name="service_code" id="shipment-service-code" value="">
<input type="hidden" name="provider_code" id="shipment-provider-code" value="<?php
if ($preselectedCarrier === 'apaczka') { echo 'apaczka'; }
elseif ($preselectedCarrier === 'polkurier') { echo 'polkurier'; }
else { echo 'allegro_wza'; }
?>">
<label class="form-field">
<span class="field-label">Typ paczki</span>
@@ -582,6 +617,8 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
var carrierSelect = document.getElementById('shipment-carrier-select');
var inpostSelect = document.getElementById('shipment-inpost-select');
var apaczkaSelect = document.getElementById('shipment-apaczka-select');
var polkurierSelect = document.getElementById('shipment-polkurier-select');
var serviceCodeInput = document.getElementById('shipment-service-code');
document.querySelectorAll('form select.form-control').forEach(function (sel) {
enhanceSelect(sel);
@@ -590,6 +627,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
var allegroPanel = document.getElementById('shipment-allegro-panel');
var inpostPanel = document.getElementById('shipment-inpost-panel');
var apaczkaPanel = document.getElementById('shipment-apaczka-panel');
var polkurierPanel = document.getElementById('shipment-polkurier-panel');
var emptyPanel = document.getElementById('shipment-empty-panel');
var wrapper = document.getElementById('shipment-service-wrapper');
@@ -608,14 +646,20 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
hiddenInput.value = '';
credentialsInput.value = '';
carrierInput.value = '';
if (serviceCodeInput) serviceCodeInput.value = '';
}
function showPanel(carrier) {
if (allegroPanel) allegroPanel.style.display = carrier === 'allegro' ? '' : 'none';
if (inpostPanel) inpostPanel.style.display = carrier === 'inpost' ? '' : 'none';
if (apaczkaPanel) apaczkaPanel.style.display = carrier === 'apaczka' ? '' : 'none';
if (polkurierPanel) polkurierPanel.style.display = carrier === 'polkurier' ? '' : 'none';
if (emptyPanel) emptyPanel.style.display = carrier === '' ? '' : 'none';
if (providerInput) providerInput.value = carrier === 'apaczka' ? 'apaczka' : 'allegro_wza';
if (providerInput) {
if (carrier === 'apaczka') providerInput.value = 'apaczka';
else if (carrier === 'polkurier') providerInput.value = 'polkurier';
else providerInput.value = 'allegro_wza';
}
}
var weekendWrap = document.getElementById('shipment-apaczka-weekend-wrap');
@@ -644,6 +688,10 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
apaczkaSelect.selectedIndex = 0;
if (apaczkaSelect._syncTrigger) apaczkaSelect._syncTrigger();
}
if (polkurierSelect) {
polkurierSelect.selectedIndex = 0;
if (polkurierSelect._syncTrigger) polkurierSelect._syncTrigger();
}
allegroOpts.forEach(function (o) { o.classList.remove('is-selected'); });
showPanel(carrierSelect.value);
toggleWeekendOption();
@@ -679,6 +727,24 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
}
toggleWeekendOption();
if (polkurierSelect) {
function syncPolkurierFields() {
var opt = polkurierSelect.options[polkurierSelect.selectedIndex];
var serviceCode = polkurierSelect.value;
hiddenInput.value = serviceCode;
if (serviceCodeInput) serviceCodeInput.value = serviceCode;
credentialsInput.value = '';
carrierInput.value = opt ? (opt.getAttribute('data-carrier-id') || serviceCode) : serviceCode;
if (providerInput) providerInput.value = 'polkurier';
}
polkurierSelect.addEventListener('change', syncPolkurierFields);
if (carrierSelect.value === 'polkurier' && polkurierSelect.value !== '') {
syncPolkurierFields();
}
}
if (wrapper && searchInput && dropdown) {
var isAllegroOpen = false;