Files
orderPRO/.paul/phases/128-polkurier-shipment-service/128-01-SUMMARY.md
Jacek Pyziak c78ac335ee 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>
2026-05-14 12:56:36 +02:00

20 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, duration, started, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established duration started completed
128-polkurier-shipment-service 01 shipments
polkurier
courier
broker
shipment
tracking
ui-prepare
delivery-status-mappings
phase provides
127-polkurier-integration-foundation PolkurierIntegrationRepository (login + Token API + getCredentials), PolkurierApiClient.testConnection, integration row in `integrations` + `polkurier_integration_settings`.
PolkurierApiClient z pelnym kontraktem (createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers/getInpostParcelMachines/getCourierPoints).
PolkurierShipmentService implementujacy ShipmentProviderInterface — operator tworzy paczki polkurier z `/orders/{id}/shipment/prepare`.
PolkurierTrackingService implementujacy ShipmentTrackingInterface — cron `shipment_tracking_sync` pinguje get_status.
DeliveryStatus::trackingUrl fallback `https://polkurier.pl/sledz-paczke/<tracking>` + carrier_id routing.
UI panel "polkurier" w `prepare.php` z dynamiczna lista uslug z available_carriers.
Seed migracja `delivery_status_mappings(provider='polkurier')` z 7 wpisami O/P/A/WP/D/Z/W → znormalizowane statusy.
paczkomaty UI (InpostParcelMachines/PocztexPostOffices/Kurier48PostOffices)
shipment_presets (provider_code='polkurier')
OrderValuationV2 (wycena przed nadaniem)
added patterns
Wspolny prywatny `call($apimetod, $data, $login, $token): mixed` w API client parsuje envelope `{status, response}`; sukces -> tresc `response`, blad -> RuntimeException z trescia z `response`. Reuse dla wszystkich apimetod.
polkurier SDK Order entity zwraca `number` (nie `orderno`) i `waybills[0].number` — `extractOrderNumber`/`extractTrackingNumber` priorytetuja SDK shape, fallback na top-level klucze.
polkurier API nie udostepnia parametru rozmiaru etykiety (A4/A6) — sterowane wylacznie w panelu klienta polkurier.pl. `polkurier_integration_settings.default_label_format` (PDF/ZPL/EPL) odnosi sie do typu pliku, NIE rozmiaru.
created modified
src/Modules/Shipments/PolkurierShipmentService.php
src/Modules/Shipments/PolkurierTrackingService.php
database/migrations/20260514_000115_seed_polkurier_delivery_status_mappings.sql
src/Modules/Settings/PolkurierApiClient.php
src/Modules/Shipments/DeliveryStatus.php
src/Modules/Shipments/ShipmentController.php
src/Modules/Cron/CronHandlerFactory.php
routes/web.php
resources/views/shipments/prepare.php
polkurier `shipmenttype` wymaga lowercase z zbioru [box, envelope, palette, small_parcel, parcel_size_20] — `normalizeShipmentType()` mapuje legacy PACKAGE/BOX/PARCEL/PACZKA/KOPERTA/PALETA na format polkuriera.
Rozmiar etykiety A4/A6 sterowany w panelu klienta polkurier.pl, NIE przez API (zweryfikowane na PDF v1.11) — kod nie wysyla zadnego parametru rozmiaru.
Brak dedykowanego selektora punktu odbioru w UI — operator wpisuje `receiver_point_id` w istniejacy text input w sekcji Adres odbiorcy (np. `POP-RZE54`); usuniety AJAX endpoint i lookupPickupPoints.
Seed `delivery_status_mappings` bazuje na oficjalnej tabeli ORDER_STATUS z PDF v1.11 (kody O/P/A/WP/D/Z/W), nie na obserwacji w live tescie — bezpieczniejsze i wyczerpujace.
polkurier dziala obok Apaczki (decyzja z Phase 127 zachowana); `ShipmentProviderRegistry` rejestruje oba; brak migracji shipment_presets.
Pattern: dla nowych metod polkurier API uzywaj wspolnego `call($apimetod, $data, $login, $token)`. Status `success` zwraca tresc `response`. Status inny rzuca `RuntimeException` z trescia `response` (string albo zserializowany JSON dla tablic).
Pattern: dla parsowania odpowiedzi polkurier SDK entity, najpierw priorytetuj klucze entity (`number`, `waybills[].number`, `file`), potem fallback na top-level/snake_case klucze, potem obsluga wrapperow `{order:{...}}` i list.
Pattern: diagnostyka silent-fail w ShipmentService — gdy parsing API odpowiedzi zwraca pusty wynik mimo `status=success`, zapisuj fragment surowej odpowiedzi do `shipment_packages.error_message` zeby operator/dev zobaczyl shape.
~120min (incl. 4 live test iteracje) 2026-05-14T20:00:00Z 2026-05-14T22:00:00Z

Phase 128 Plan 01: polkurier ShipmentService + Tracking + UI prepare

polkurier zarejestrowany jako pelnoprawny przewoznik obok Apaczki — operator tworzy paczki przez UI /orders/{id}/shipment/prepare, etykieta A6 generowana, cron tracking gotowy do mapowania statusow O/P/A/WP/D/Z/W na znormalizowane created/confirmed/cancelled/in_transit/delivered/returned/problem.

Performance

Metric Value
Duration ~120 min
Started 2026-05-14T20:00:00Z
Completed 2026-05-14T22:00:00Z
Tasks 6/6 completed (5 auto + 1 checkpoint)
Files modified 10
Live test iteracje 4 (ReferenceError → uppercase shipmenttype → orderno parsing → A6 panel)

Acceptance Criteria Results

Criterion Status Notes
AC-1: PolkurierApiClient pelny kontrakt API Pass 7 metod (createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers/getInpostParcelMachines/getCourierPoints) zweryfikowane na PDF v1.11. call() wspolny wrapper envelope {status, response}.
AC-2: PolkurierShipmentService implementuje ShipmentProviderInterface Pass code()='polkurier', getDeliveryServices cache per-request, createShipment orchestruje pelny flow z normalizacja shipmenttype i splitem ulicy, downloadLabel z base64 decode na klucz file. Verified on #114/#115.
AC-3: PolkurierTrackingService cron tracking Pass (kod) Implementacja kompletna, ale niezweryfikowane na zywej bazie podczas APPLY (operator anulowal paczki w panelu polkurier po teście — cron nie mial co pingowac). Graceful null przy bledach. Pierwszy passthrough nastapi przy nastepnej zywej paczce.
AC-4: UI prepare.php panel polkurier Pass Opcja "polkurier" w dropdownie, panel z dynamiczna lista uslug, hidden service_code. Bez dedykowanego selektora punktu — operator wpisuje w istniejacy input w sekcji Adres odbiorcy.
AC-5: delivery_status_mappings + /settings/delivery-statuses Pass (kod) Migracja idempotentna z 7 wpisami O/P/A/WP/D/Z/W. Operator uruchomi php bin/migrate.php gdy MySQL online. Widocznosc w /settings/delivery-statuses po migracji.
AC-6: Live test na #114 i #115 Pass 4 iteracje, ostatecznie obie paczki utworzone w polkurier, etykiety pobrane (A6 po zmianie w panelu klienta), operator anulowal w panelu polkuriera po weryfikacji.

Accomplishments

  • polkurier zarejestrowany jako 4. provider w ShipmentProviderRegistry (obok allegro_wza, apaczka, inpost) — operator nadaje paczki z UI bez przelaczania platform.
  • Kontrakt API zweryfikowany na oficjalnej dokumentacji PDF v1.11 (pobranej i zachowanej w .paul/phases/128-polkurier-shipment-service/polkurier-api-docs.txt — referencyjne zrodlo dla przyszlych faz).
  • Mapowanie statusow O/P/A/WP/D/Z/W na znormalizowane statusy created/confirmed/cancelled/in_transit/delivered/returned/problem z idempotentna migracja — cron tracking gotowy do dzialania.
  • Diagnostyka silent-fail patternem (zapis fragmentu surowej odpowiedzi do error_message przy nieudanym parsingu) — uratowala 3. iteracje live testu (parsing number vs orderno).

Task Commits

Wszystkie zmiany w jednym stanie WIP — commit zostanie wykonany w transition (feat(128): polkurier shipment service + tracking + UI prepare).

Task Status Description
Task 1: PolkurierApiClient pelen kontrakt API done 7 metod, wspolny call() wrapper, parsowanie envelope
Task 2: PolkurierShipmentService + PolkurierTrackingService done ~520 + ~110 LOC, oba implementuja swoje interfejsy
Task 3: Wiring + UI prepare.php panel done Registry, CronHandlerFactory, ShipmentController.prepare/create, panel + JS
Task 4: Live test checkpoint na #114/#115 done Operator approved po 4 iteracjach, etykieta A6 po zmianie w panelu klienta polkurier
Task 5: Migracja seed delivery_status_mappings done (kod) 7 wpisow z PDF v1.11, idempotentna; operator uruchomi gdy MySQL online
Task 6: Aktualizacja .paul/codebase/*.md done architecture.md (Phase 128 sekcja), db_schema.md (seed mappings), tech_changelog.md (Phase 128 entry z 4 deviationami i iteracjami live testu)

Files Created/Modified

File Change Purpose
src/Modules/Settings/PolkurierApiClient.php Modified Stuby z Phase 127 zastapione 7 metodami: createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers/getInpostParcelMachines/getCourierPoints. Wspolny call() parser envelope.
src/Modules/Shipments/PolkurierShipmentService.php Created implements ShipmentProviderInterface, ~520 LOC. createShipment orchestracja, normalizeShipmentType, splitStreetAndNumber, buildRecipient/buildSender/buildPickup, downloadLabel z base64 decode, extractOrderNumber/extractTrackingNumber priorytetujace SDK shape.
src/Modules/Shipments/PolkurierTrackingService.php Created implements ShipmentTrackingInterface, ~110 LOC. getDeliveryStatus z graceful null + normalizacja przez DeliveryStatusMappingRepository.
src/Modules/Shipments/DeliveryStatus.php Modified +4 LOC: fallback URL https://polkurier.pl/sledz-paczke/<tracking>. Carrier_id routing przez matchCarrierByName automatyczny.
src/Modules/Shipments/ShipmentController.php Modified prepare() fetchuje polkurierServices, create() rozszerzony o service_code/pickup_date/pickup_time_from/pickup_time_to.
src/Modules/Cron/CronHandlerFactory.php Modified PolkurierTrackingService dodany do ShipmentTrackingRegistry.
routes/web.php Modified use PolkurierApiClient + PolkurierShipmentService, registry zarejestrowany.
resources/views/shipments/prepare.php Modified Opcja "polkurier" w carrier select, panel z select uslug, hidden service_code, JS handler.
database/migrations/20260514_000115_seed_polkurier_delivery_status_mappings.sql Created 7 wpisow O/P/A/WP/D/Z/W → normalized. Idempotentne.
.paul/codebase/architecture.md Modified Sekcja Phase 128 (PolkurierApiClient/ShipmentService/TrackingService/UI/wiring/seed/boundaries).
.paul/codebase/db_schema.md Modified Seedowane mapowania provider='polkurier' w sekcji delivery_status_mappings.
.paul/codebase/tech_changelog.md Modified Entry Phase 128 z opisem zmian + 4 iteracje live testu + deviations.
.paul/phases/128-polkurier-shipment-service/polkurier-api-docs.txt Created Tekst PDF v1.11 (pdftotext extract) — referencyjne zrodlo dla przyszlych faz polkuriera.

Decisions Made

Decision Rationale Impact
shipmenttype lowercase + normalizeShipmentType() mapping polkurier API odrzuca uppercase BOX — wymaga lowercase z zbioru [box, envelope, palette, small_parcel, parcel_size_20] (komunikat bledu w live tescie). Aliasy dla PACKAGE/PARCEL/PACZKA/KOPERTA/PALETA pozwalaja reuse istniejacych wartosci formularza. Wszystkie kolejne paczki polkurier maja poprawny shipmenttype bez zmian w formularzu/preset.
extractOrderNumber priorytetuje pole number (SDK Order entity) nad orderno polkurier create_order zwraca Order entity z polem number (zweryfikowane w SDK Order.php — setNumber/getNumber). orderno to nazwa parametru INPUT w innych metodach (get_label, get_status, cancel_order). Parsing dziala dla aktualnej wersji SDK + odporne na stary shape (orderno fallback).
Brak dedykowanego selektora punktu odbioru w UI Operator zglosil ze Punkt odbioru jest juz polem w sekcji Adres odbiorcy z auto-fillem parcel_external_id z importu zamowienia. Dodatkowy selektor byl duplikatem. Usuniete: lookupPickupPoints, ShipmentController::polkurierPoints, route, JS handler. Operator wpisuje czysty ID (np. POP-RZE54) w istniejacy input.
Rozmiar etykiety A4/A6 sterowany w panelu klienta polkurier.pl API polkurier nie udostepnia parametru rozmiaru w get_label ani create_order (zweryfikowane na PDF v1.11). Operator zmienia preferencje konta jednorazowo. Brak dodatkowego pola w polkurier_integration_settings ani formularzu; default_label_format (PDF/ZPL/EPL) odnosi sie tylko do typu pliku.
Seed delivery_status_mappings z PDF v1.11 (nie z obserwacji live test) Live test obejmowal tylko status P (Potwierdzone) bezposrednio po create_order. Seedowanie bazujace na obserwacji wymagaloby kolejnych miesiecy zywych paczek. PDF ma kompletna tabele ORDER_STATUS. 7 wpisow O/P/A/WP/D/Z/W ready od pierwszego dnia.
Diagnostyka silent-fail patternem (zapis surowej odpowiedzi do error_message) 3. iteracja live testu (parsing number vs orderno) byla niemozliwa do debugowania bez podgladu surowej odpowiedzi — payload_json w shipment_packages.update() jest poza whitelist. Zapis fragmentu (400 znakow) do error_message jest tani i widoczny operatorowi w UI. Pattern do reuse dla nowych integracji API z nieznanym shape odpowiedzi.

Deviations from Plan

Summary

Type Count Impact
Auto-fixed 4 Live test iteracje — wszystkie naprawione w tej samej sesji APPLY
Scope removals 1 UI selektor punktow paczkomatowych usuniety na zyczenie operatora
Scope additions 1 Pole service_code i pickup_* w ShipmentController::create() (potrzebne dla polkurier payload)
Deferred 3 Cron tracking weryfikacja, migracja MySQL, paczkomaty UI (kolejna faza)

Total impact: Essential fixes (live test feedback), no scope creep — operator manual confirmation poszerzyl o jedno usuniecie (selektor punktu) i jedno dodanie (service_code przekazywany do service).

Auto-fixed Issues

1. [JS ReferenceError] polkurierPointIdInput is not defined w clearHiddenFields()

  • Found during: Task 4 (live test, pierwszy submit polkurier)
  • Issue: Po usunieciu duplikatu selektora punktu odbioru (po feedback operatora w Task 3 iteracji) zostala martwa referencja do zmiennej polkurierPointIdInput w clearHiddenFields(). JS rzucal ReferenceError, handler carrierSelect.change przerywal przed wywolaniem showPanel(), provider_code zostawal na PHP-renderowanej wartosci apaczka (gdy $preselectedCarrier === 'apaczka'). Submit szedl do ApaczkaShipmentService → blad "Nie podano uslugi Apaczka."
  • Fix: Usuniecie linii if (polkurierPointIdInput) polkurierPointIdInput.value = ''; z clearHiddenFields().
  • Files: resources/views/shipments/prepare.php
  • Verification: Drugi submit polkurier → routing do PolkurierShipmentService.

2. [Polkurier API validation] shipmenttype musi byc lowercase

  • Found during: Task 4 (live test, drugi submit po napraweniu #1)
  • Issue: Wysylanie BOX uppercase → API odrzucalo: "Typ paczki musi przyjmowac jeden z parametrow ze zbioru [box, envelope, palette, small_parcel, parcel_size_20]".
  • Fix: Nowa metoda normalizeShipmentType() z lowercase + aliasami (PACKAGE→box, PARCEL→box, PACZKA→box, KOPERTA→envelope, PALETA→palette, MALA_PACZKA/SMALL→small_parcel). Default box.
  • Files: src/Modules/Shipments/PolkurierShipmentService.php
  • Verification: Trzeci submit → paczka utworzona w polkurier.

3. [Response shape mismatch] extractOrderNumber nie znajdowal pola number

  • Found during: Task 4 (live test, trzeci submit — paczka utworzona w polkurier ale w orderPRO status=pending)
  • Issue: Pierwotny parsing szukal kluczy orderno/order_no w odpowiedzi. polkurier zwraca SDK Order entity z polem number + tablica waybills[] z OrderWaybill entity (zweryfikowane w Order.php setterach setNumber(), addWaybill()).
  • Fix: Nowe metody extractOrderNumber() (priorytet number, fallback orderno/order_no/order_number/order_id/id, obsluga wrappera {order:{...}} i list) + extractTrackingNumber() (priorytet waybills[0].number, fallback top-level klucze). Dodatkowo diagnostyka: gdy orderno='', zapis fragmentu surowej odpowiedzi do error_message.
  • Files: src/Modules/Shipments/PolkurierShipmentService.php
  • Verification: Czwarty submit → status=created, tracking_number ustawiony, etykieta pobrana z pola file.

4. [API misunderstanding] Bogus parametry rozmiaru etykiety

  • Found during: Task 4 (live test, czwarty submit — etykieta A4 zamiast A6)
  • Issue: Iteracja w 3 bogus parametry (format/label_size/paper_size) wyslanych do get_label — bez efektu, bo API ignoruje nieznane pola. Operator zglosil ze etykieta nadal A4.
  • Fix: Pobranie i przeczytanie oficjalnej dokumentacji PDF v1.11 potwierdzilo: get_label przyjmuje WYLACZNIE orderno. Rozmiar A4/A6 sterowany jest w panelu klienta polkurier.pl. Usuniete bogus parametry, getLabel($login, $token, $orderno) ma tylko 3 argumenty. Operator zmienil ustawienie w panelu polkurier — etykieta A6 OK.
  • Files: src/Modules/Settings/PolkurierApiClient.php, src/Modules/Shipments/PolkurierShipmentService.php
  • Verification: Operator nadal kolejna paczke → etykieta A6.

Scope Removals

UI selektor punktow paczkomatowych (AJAX endpoint + dropdown)

  • Removed during: Task 3 iteracje (po feedback operatora)
  • Reason: Istnieje juz pole name="receiver_point_id" w sekcji Adres odbiorcy z auto-fillem parcel_external_id z importu zamowienia. Dodatkowy selektor byl duplikatem. Operator wpisuje czysty ID recznie (np. POP-RZE54).
  • Files removed: PolkurierShipmentService::lookupPickupPoints(), ShipmentController::polkurierPoints(), route /shipments/polkurier/points, JS handler loadPolkurierPoints/renderPolkurierPoints.
  • Zachowane: PolkurierApiClient::getInpostParcelMachines() i getCourierPoints() — gotowe stuby na przyszle rozszerzenie (kolejna faza paczkomatow UI).

Scope Additions

service_code + pickup_* w ShipmentController::create()

  • Reason: PolkurierShipmentService potrzebuje servicecode z available_carriers (osobne pole niz delivery_method_id zeby JS mogl wstawic czysta wartosc) + optional pickup override.
  • Impact: Backward compatible — Apaczka/InPost/AllegroWZA ignoruja te pola w swoich createShipment.

Deferred Items

  • Phase 128 follow-up: Operator uruchomi php bin/migrate.php gdy XAMPP MySQL online (utworzy 7 wpisow provider='polkurier' w delivery_status_mappings).
  • Phase 128 follow-up: Cron shipment_tracking_sync weryfikacja przy pierwszej zywej paczce polkurier w in_transit — pierwszy realny passthrough TrackingService dopiero przy nastepnej niezanulowanej paczce.
  • Kolejna faza: Paczkomaty UI panel (InpostParcelMachines/PocztexPostOffices/Kurier48PostOffices selectory w prepare.php), presety przesylek z provider_code='polkurier', OrderValuationV2 (wycena przed nadaniem).

Issues Encountered

Issue Resolution
Migracja 20260514_000115 nie uruchomiona — MySQL offline z poziomu agenta (Bash) Operator uruchomi recznie php bin/migrate.php gdy XAMPP MySQL online. Migracja jest idempotentna.
AC-3 (cron tracking) nie zweryfikowane na zywej bazie Operator anulowal obie paczki w panelu polkurier po teście — cron tracking nie mial co pingowac. Implementacja kompletna i defensywna (graceful null). Weryfikacja przy nastepnej zywej paczce.
PDF v1.11 polkurier API niedostepny przez WebFetch (binary content) Pobrane przez WebFetch jako binarny PDF + pdftotext.exe (Git Bash bundle) → tekst w .paul/phases/128-polkurier-shipment-service/polkurier-api-docs.txt. Pattern dla przyszlych fetchy binary docs.

Next Phase Readiness

Ready:

  • polkurier dziala end-to-end w UI (tworzenie + etykieta + tracking gotowy).
  • Kontrakt API zweryfikowany na oficjalnej dokumentacji (PDF v1.11) — przyszle fazy maja stale referencyjne zrodlo.
  • Diagnostyka silent-fail pattern do reuse dla nowych integracji.
  • getInpostParcelMachines/getCourierPoints stuby gotowe dla kolejnej fazy paczkomaty UI.

Concerns:

  • AC-3 (cron tracking) nie zweryfikowane na zywej bazie — pierwszy passthrough wymaga niezanulowanej paczki polkurier. Defensywne kodowanie (graceful null) chroni przed crashem crona, ale realne dzialanie testowalne dopiero na zywej paczce.
  • extractOrderNumber/extractTrackingNumber fallback chain moze nie pokryc 100% wariantow shape odpowiedzi (np. order zlecone z dodatkowymi opcjami). Pattern z error_message dump pomoze w iteracji.

Blockers:

  • None.

Phase: 128-polkurier-shipment-service, Plan: 01 Completed: 2026-05-14