From c78ac335ee6f03f07685525984e2c45365859462 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Thu, 14 May 2026 12:56:36 +0200 Subject: [PATCH] feat(128): polkurier shipment service + tracking + UI prepare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .paul/PROJECT.md | 12 +- .paul/ROADMAP.md | 4 +- .paul/STATE.md | 29 +- .paul/changelog/2026-05-14.md | 27 + .paul/codebase/architecture.md | 74 + .paul/codebase/db_schema.md | 12 + .paul/codebase/tech_changelog.md | 36 + .../128-01-PLAN.md | 392 +++ .../128-01-SUMMARY.md | 221 ++ .../polkurier-api-docs.txt | 2963 +++++++++++++++++ ...eed_polkurier_delivery_status_mappings.sql | 27 + resources/views/shipments/prepare.php | 70 +- routes/web.php | 9 + src/Modules/Cron/CronHandlerFactory.php | 8 + src/Modules/Settings/PolkurierApiClient.php | 327 +- src/Modules/Shipments/DeliveryStatus.php | 4 + .../Shipments/PolkurierShipmentService.php | 760 +++++ .../Shipments/PolkurierTrackingService.php | 120 + src/Modules/Shipments/ShipmentController.php | 18 + 19 files changed, 5011 insertions(+), 102 deletions(-) create mode 100644 .paul/phases/128-polkurier-shipment-service/128-01-PLAN.md create mode 100644 .paul/phases/128-polkurier-shipment-service/128-01-SUMMARY.md create mode 100644 .paul/phases/128-polkurier-shipment-service/polkurier-api-docs.txt create mode 100644 database/migrations/20260514_000115_seed_polkurier_delivery_status_mappings.sql create mode 100644 src/Modules/Shipments/PolkurierShipmentService.php create mode 100644 src/Modules/Shipments/PolkurierTrackingService.php diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index 072b694..ab1efad 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -13,8 +13,8 @@ Sprzedawca moĹĽe obsĹ‚ugiwać zamĂłwienia ze wszystkich kanałów | Attribute | Value | |-----------|-------| | Version | 3.7.0-dev | -| Status | v3.7 in progress — Phases 113-127 shipped (Fakturownia + HostedSMS/SMSPLANET + Alert unify + receipt VAT + SMS templates + invoice_requested import fix + invoice GUS mapping + polkurier foundation) | -| Last Updated | 2026-05-14 (Phase 127 closed) | +| Status | v3.7 in progress — Phases 113-128 shipped (Fakturownia + HostedSMS/SMSPLANET + Alert unify + receipt VAT + SMS templates + invoice_requested import fix + invoice GUS mapping + polkurier foundation + polkurier shipment service) | +| Last Updated | 2026-05-14 (Phase 128 closed) | ## Requirements @@ -127,6 +127,7 @@ Sprzedawca moĹĽe obsĹ‚ugiwać zamĂłwienia ze wszystkich kanałów - [x] Szablony SMS: CRUD w `/settings/sms-templates` (name + body + is_active), wspolny `SmsVariableResolver` wydzielony z Email\\VariableResolver (placeholdery `{{zamowienie.*|kupujacy.*|adres.*|firma.*|przesylka.*}}`), dropdown "Wybierz szablon" w zakladce SMS na `/orders/{id}` wstawia rozwiniete zmienne do textarea (z `OrderProAlerts.confirm` przy nadpisaniu); stopka SMSPLANET dalej doklejana wylacznie przez `SmsConversationService::buildFinalOutboundBody()` (Phase 122 contract preserved) — Phase 124 - [x] Bugfix detekcji faktury przy imporcie: shopPRO order z `firm_nip` ustawia `invoice_requested=1` (mapper jako jedyne zrodlo heurystyki, sync service propaguje `aggregate['invoice_detected']`); Allegro rozszerzony o `naturalPerson=false`/`address.taxId`/`companyName` (wczesniej tylko `invoice.required`); usunieta legacy kolumna `orders.is_invoice` (Phase 115 dryft) + backfill 7 zamowien — Phase 125 - [x] Integracja polkurier.pl (fundament): pojedyncza globalna konfiguracja w `/settings/integrations/polkurier`, szyfrowany Token API + login, karta w hubie integracji obok Apaczki i realny test polaczenia przez `apimetod=test_auth_api` zweryfikowany na zywym koncie operatora; `ShipmentProviderRegistry` netkniety — `PolkurierShipmentService/TrackingService` w kolejnych fazach — Phase 127 +- [x] polkurier ShipmentService + TrackingService + UI prepare panel: pelen kontrakt API (createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers), `PolkurierShipmentService` implementujacy `ShipmentProviderInterface` z normalizacja shipmenttype (lowercase) i splitem ulicy na street/housenumber/flatnumber, `PolkurierTrackingService` mapujacy statusy O/P/A/WP/D/Z/W na znormalizowane, panel "polkurier" w `prepare.php` z dynamiczna lista uslug z `available_carriers`, seed migracja `delivery_status_mappings(provider='polkurier')` z 7 wpisami z PDF v1.11; live test na #114/#115 zakonczony sukcesem po 4 iteracjach (ReferenceError → uppercase shipmenttype → orderno parsing → A4/A6); rozmiar etykiety sterowany w panelu klienta polkurier.pl (Ustawienia konta → Preferencje etykiet), NIE przez API — Phase 128 ### Deferred @@ -243,6 +244,11 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API | polkurier API: top-level `status` === `'success'` (nie `'ok'`), tresc bledu w polu `response` envelope'a | `ResponseStatus::SUCCESS = 'success'` z `src/Type/ResponseStatus.php` SDK; bledy rzucane przez `ErrorException($response->get('response'))` w `PolkurierWebService.php`. Pattern dla wszystkich przyszlych metod polkurier API (`createShipment`, `getLabel`, `getStatus`, `cancelOrder`, etc.) | 2026-05-14 | Active | | polkurier API odrzuca `Content-Type` z parametrem (`application/json; charset=UTF-8`) — wymagany dokladnie `application/json` | Strict equality check po stronie polkuriera; pattern do reuse jezeli inne integracje sa rownie strict | 2026-05-14 | Active | | polkurier dziala obok Apaczki (nie zamiast) | Decyzja operatora — oba dostawcy zyja niezaleznie, `ShipmentProviderRegistry` rejestruje obu (Apaczka netknieta w Phase 127; polkurier dodany w nastepnej fazie razem z `PolkurierShipmentService`) | 2026-05-14 | Active | +| polkurier `shipmenttype` wymaga lowercase z zbioru `[box, envelope, palette, small_parcel, parcel_size_20]` | API odrzuca uppercase `BOX` (komunikat: "Typ paczki musi przyjmowac jeden z parametrow ze zbioru ..."). `normalizeShipmentType()` w `PolkurierShipmentService` mapuje legacy PACKAGE/BOX/PARCEL/PACZKA/KOPERTA/PALETA na format polkuriera z aliasami i defaultem `box`. | 2026-05-14 | Active | +| polkurier `create_order` zwraca Order entity z polem `number` (nie `orderno`) i `waybills[0].number` | SDK Order.php uzywa setNumber()/addWaybill() — JSON shape entity, nie parametrow input. `extractOrderNumber` priorytetuje `number`, fallback na `orderno`/`order_no`/`order_number`/`order_id`/`id` + obsluga wrapperow `{order:{...}}` i list. `extractTrackingNumber` priorytetuje `waybills[0].number`. Pattern do reuse dla innych metod polkurier SDK. | 2026-05-14 | Active | +| polkurier API nie ma parametru rozmiaru etykiety (A4/A6) | Zweryfikowane na PDF v1.11: `get_label` przyjmuje wylacznie `orderno: Array`, `create_order` nie ma pola format/size. Rozmiar sterowany w panelu klienta polkurier.pl (Ustawienia konta → Preferencje etykiet) — operator zmienia preferencje konta jednorazowo. `polkurier_integration_settings.default_label_format` (PDF/ZPL/EPL) odnosi sie tylko do typu pliku, NIE rozmiaru. | 2026-05-14 | Active | +| Brak dedykowanego selektora punktow paczkomatowych w UI polkurier (Phase 128) | Istnieje juz pole `name="receiver_point_id"` w sekcji Adres odbiorcy z auto-fillem `parcel_external_id` z importu zamowienia — operator wpisuje ID recznie (np. `POP-RZE54`). Usuniete: `lookupPickupPoints`/`ShipmentController::polkurierPoints`/AJAX route/JS handler. `getInpostParcelMachines`/`getCourierPoints` zachowane jako stuby w API client — gotowe dla kolejnej fazy paczkomaty UI. | 2026-05-14 | Active | +| Diagnostyka silent-fail w ShipmentService — zapis surowej odpowiedzi do `error_message` | Gdy parsing API odpowiedzi zwraca pusty wynik mimo `status=success` (np. nieznany shape pola order number), zapisuj fragment surowej odpowiedzi (400 znakow) do `shipment_packages.error_message` — widoczne operatorowi w UI bez podgladu serwerowych logow. Pattern uratowal 3. iteracje live testu Phase 128. Reuse dla nowych integracji z API o nieznanym shape odpowiedzi. | 2026-05-14 | Active | ## Success Metrics @@ -274,6 +280,6 @@ Quick Reference: --- *PROJECT.md — Updated when requirements or context change* -*Last updated: 2026-05-14 after Phase 127 (polkurier Integration Foundation) closure; v3.7 milestone in progress* +*Last updated: 2026-05-14 after Phase 128 (polkurier ShipmentService + Tracking + UI prepare) closure; v3.7 milestone in progress* diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 0556d90..3122916 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -27,9 +27,9 @@ Wystawianie faktur dla klientow z NIP poprzez integracje z Fakturownia (app.fakt | 125 | invoice_requested Import Fix (shopPRO+Allegro NIP detection, drop legacy is_invoice column) | 1/1 | Complete (2026-05-13; migration + manual smoke pending operator) | | 126 | Invoice GUS Field Mapping Fix (KRS-based heuristic: JDG → name do "Imię i nazwisko", spółka → "Nazwa firmy") | 1/1 | Complete (2026-05-13; manual smoke pending operator) | | 127 | polkurier Integration Foundation (single-instance settings + Token API + realny test polaczenia; obok Apaczki) | 1/1 | Complete (2026-05-14; live API verified — `Autoryzacja: 1`) | +| 128 | polkurier ShipmentService + TrackingService + UI prepare panel + delivery_status_mappings seed (live test na #114/#115) | 1/1 | Complete (2026-05-14; live test passed po 4 iteracjach; migracja + cron tracking weryfikacja pending) | Planowane kolejne fazy v3.7 (kandydaci, do rozplanowania): -- polkurier ShipmentService (CreateOrder + GetLabel + OrderValuationV2 + AvailableCarriers mapping + UI mapowan metod dostawy + presety) — fundament 127 zweryfikowany - polkurier TrackingService + `delivery_status_mappings` (provider='polkurier') - polkurier paczkomaty (`InpostParcelMachines` / `PocztexPostOffices` / `Kurier48PostOffices` z SDK polkuriera) - Eksport XLSX listy wystawionych faktur (analogicznie do paragonow) @@ -512,4 +512,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md` --- *Roadmap created: 2026-03-12* -*Last updated: 2026-05-14 - Phase 127 UNIFY closed (live API verified)* +*Last updated: 2026-05-14 - Phase 128 UNIFY closed (live test passed, etykiety A6 OK)* diff --git a/.paul/STATE.md b/.paul/STATE.md index 04edbdb..459312e 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -5,33 +5,33 @@ See: .paul/PROJECT.md (updated 2026-05-07) **Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami. -**Current focus:** v3.7 Invoices + operational integrations - Phase 127 polkurier foundation UNIFY zakonczony, transition (commit + ROADMAP/PROJECT update) pending. +**Current focus:** v3.7 Invoices + operational integrations - Phase 128 polkurier ShipmentService loop closed, transition pending (git commit + ROADMAP/PROJECT update). ## Current Position Milestone: v3.7 Invoices (Fakturownia integration) - In progress -Phase: 127 of TBD (polkurier Integration Foundation) - Complete -Plan: 127-01 complete (SUMMARY.md created); live API verified (`Autoryzacja: 1`) -Status: UNIFY complete, transition pending (git commit + decisions in PROJECT.md) -Last activity: 2026-05-14 - Phase 127-01 UNIFY zakonczony, SUMMARY + changelog utworzone +Phase: 128 of TBD (polkurier ShipmentService + Tracking + UI) - Complete +Plan: 128-01 complete (SUMMARY.md created) +Status: UNIFY complete, transition pending (git commit + Decisions w PROJECT.md + ROADMAP status) +Last activity: 2026-05-14 - Phase 128-01 UNIFY zakonczony, SUMMARY + changelog utworzone Progress: -- Milestone v3.7: [##########] ~99% (Phase 113-127 complete; transition pending) -- Phase 127: [##########] 100% +- Milestone v3.7: [##########] ~99% (Phase 113-128 complete; transition pending) +- Phase 128: [##########] 100% ## Loop Position Current loop state: ``` PLAN -> APPLY -> UNIFY - done done done [Loop complete - transition pending] + done done done [Loop complete - transition pending] ``` ## Session Continuity Last session: 2026-05-14 -Stopped at: Phase 127-01 UNIFY closed; SUMMARY.md created -Next action: Phase transition (git commit `feat(127): polkurier integration foundation` + Decisions w PROJECT.md), potem wybor kolejnego kandydata v3.7 (np. PolkurierShipmentService albo invoice.created event) +Stopped at: Phase 128-01 UNIFY closed; SUMMARY.md created +Next action: Phase transition (git commit `feat(128): polkurier shipment service + tracking + UI prepare` + Decisions w PROJECT.md + ROADMAP status update), potem wybor kolejnego kandydata v3.7 (paczkomaty UI / shipment_presets polkurier / OrderValuationV2 / invoice.created event / eksport XLSX faktur) Resume file: .paul/ROADMAP.md ## Pending parallel work @@ -39,9 +39,9 @@ Resume file: .paul/ROADMAP.md ## Git State -Last phase commit: c758ec7 feat(126): invoice GUS field mapping fix (JDG/KRS heuristic) -Previous: 2ab461a feat(125): invoice_requested import fix + drop legacy is_invoice column -Branch: main (5 commits ahead of origin/main) +Last phase commit: 3443879 feat(127): polkurier integration foundation +Previous: c758ec7 feat(126): invoice GUS field mapping fix (JDG/KRS heuristic) +Branch: main (6 commits ahead of origin/main) ## Pending Actions @@ -68,6 +68,9 @@ Branch: main (5 commits ahead of origin/main) - Phase 127 follow-up: zaplanowac kolejna faze polkurier — `PolkurierShipmentService` (CreateOrder + GetLabel + OrderValuationV2 + AvailableCarriers mapping + UI mapowan metod dostawy + presety przesylek) — fundament + zweryfikowany kontrakt API gotowy. - Phase 127 follow-up: drugi krok — `PolkurierTrackingService` + wpisy w `delivery_status_mappings` (provider='polkurier'). - Phase 127 follow-up: po polkurier shipment service rozwazyc fazy paczkomaty (`InpostParcelMachines` / `PocztexPostOffices` / `Kurier48PostOffices` API juz dostepne w SDK polkuriera). +- Phase 128 follow-up: uruchom migracje gdy XAMPP MySQL online: `php bin/migrate.php` (seed 7 wpisow `provider='polkurier'` w `delivery_status_mappings`). +- Phase 128 follow-up: weryfikacja crona `shipment_tracking_sync` przy pierwszej zywej paczce polkurier w `in_transit` — sprawdz ze `shipment_packages.delivery_status` aktualizuje sie z `D`/`WP`/`Z` przez `DeliveryStatus::normalizeWithOverrides('polkurier', ...)`. +- Phase 128 follow-up: rozmiar etykiety A4 vs A6 sterowany jest w panelu klienta polkurier.pl (Ustawienia konta → Preferencje etykiet), NIE przez API — operator ustawil A6. ## Deferred to Next Milestones diff --git a/.paul/changelog/2026-05-14.md b/.paul/changelog/2026-05-14.md index 952f7fb..bbcba34 100644 --- a/.paul/changelog/2026-05-14.md +++ b/.paul/changelog/2026-05-14.md @@ -26,3 +26,30 @@ - `.paul/ROADMAP.md` - `.paul/phases/127-polkurier-integration-foundation/127-01-PLAN.md` - `.paul/phases/127-polkurier-integration-foundation/127-01-SUMMARY.md` + +## Co zrobiono (cd.) + +- [Phase 128, Plan 01] polkurier ShipmentService + TrackingService + UI prepare panel + delivery_status_mappings seed. polkurier zarejestrowany jako 4. provider w `ShipmentProviderRegistry` (obok allegro_wza/apaczka/inpost). Operator tworzy paczki z `/orders/{id}/shipment/prepare`, etykieta A6 generowana, cron tracking gotowy do mapowania O/P/A/WP/D/Z/W → created/confirmed/cancelled/in_transit/delivered/returned/problem. +- Task 1: `PolkurierApiClient` rozszerzony z stubów Phase 127 do 7 metod (createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers/getInpostParcelMachines/getCourierPoints). Wspólny prywatny `call($apimetod, $data, $login, $token)` parsuje envelope `{status, response}`. Kontrakt zweryfikowany na oficjalnej dokumentacji PDF v1.11 (pobrana i zachowana w `.paul/phases/128-polkurier-shipment-service/polkurier-api-docs.txt`). +- Task 2: `PolkurierShipmentService` (~520 LOC, implements ShipmentProviderInterface) + `PolkurierTrackingService` (~110 LOC, implements ShipmentTrackingInterface). `normalizeShipmentType()` mapuje legacy PACKAGE/BOX/PARCEL na lowercase zbiór polkuriera. `extractOrderNumber`/`extractTrackingNumber` priorytetują SDK Order entity (`number`, `waybills[0].number`). +- Task 3: Wiring `routes/web.php` + `CronHandlerFactory` + `ShipmentController.prepare/create` (rozszerzony o `service_code`/`pickup_*` w form data). UI panel "polkurier" w `prepare.php` z dynamiczną listą usług + JS toggle. `DeliveryStatus::trackingUrl` fallback dla provider='polkurier'. +- Task 4 (checkpoint live test #114/#115): 4 iteracje — ReferenceError w JS `clearHiddenFields` → uppercase `shipmenttype` → parsing `number` vs `orderno` → A4 vs A6 etykieta. Każda iteracja autopoprawiona w tej samej sesji APPLY. +- Task 5: Migracja `20260514_000115_seed_polkurier_delivery_status_mappings.sql` z 7 wpisami z oficjalnej tabeli ORDER_STATUS PDF v1.11 (O/P/A/WP/D/Z/W). Idempotentna `ON DUPLICATE KEY UPDATE`. +- Task 6: Aktualizacja `.paul/codebase/{architecture,db_schema,tech_changelog}.md` z sekcją Phase 128. +- Scope removal vs PLAN: UI selektor punktów paczkomatowych usunięty (operator zgłosił duplikat z polem "Punkt odbioru" w sekcji Adres odbiorcy). `lookupPickupPoints` + AJAX route + JS handler usunięte. `getInpostParcelMachines`/`getCourierPoints` zostawione jako stuby na przyszłą fazę paczkomatów UI. +- Decyzja: rozmiar etykiety A4/A6 sterowany w panelu klienta polkurier.pl, NIE przez API (zweryfikowane w PDF v1.11). Operator zmienia preferencje konta jednorazowo. + +## Zmienione pliki (cd.) + +- `src/Modules/Settings/PolkurierApiClient.php` (rozszerzenie z stubów do 7 metod) +- `src/Modules/Shipments/PolkurierShipmentService.php` (nowy plik) +- `src/Modules/Shipments/PolkurierTrackingService.php` (nowy plik) +- `src/Modules/Shipments/DeliveryStatus.php` (fallback URL polkurier) +- `src/Modules/Shipments/ShipmentController.php` (polkurierServices + service_code/pickup_*) +- `src/Modules/Cron/CronHandlerFactory.php` (rejestracja PolkurierTrackingService) +- `routes/web.php` (rejestracja PolkurierShipmentService w registry) +- `resources/views/shipments/prepare.php` (panel polkurier + JS) +- `database/migrations/20260514_000115_seed_polkurier_delivery_status_mappings.sql` (nowy plik) +- `.paul/phases/128-polkurier-shipment-service/128-01-PLAN.md` (nowy plik) +- `.paul/phases/128-polkurier-shipment-service/128-01-SUMMARY.md` (nowy plik) +- `.paul/phases/128-polkurier-shipment-service/polkurier-api-docs.txt` (nowy plik — referencyjna doca z PDF v1.11) diff --git a/.paul/codebase/architecture.md b/.paul/codebase/architecture.md index c9108e1..9e3b066 100644 --- a/.paul/codebase/architecture.md +++ b/.paul/codebase/architecture.md @@ -378,6 +378,80 @@ tests/ - `apaczka_integration_settings`, `ApaczkaShipmentService`, `ApaczkaTrackingService` — Apaczka netknieta, dziala rownolegle. - `delivery_status_mappings` — brak nowych wpisow `provider='polkurier'` (dolozone razem z tracking service w kolejnej fazie). +## Phase 128 — polkurier ShipmentService + Tracking + UI prepare + +### PolkurierApiClient (Phase 128 extension) +- 6 nowych metod publicznych obok zachowanego `testConnection()`: + - `getAvailableCarriers($login, $token)` → `apimetod=available_carriers`. Zwraca tablice przewoznikow z polami `servicecode`, `name`, `additional_data`, `foreign_shipments`. Konsumowane przez `PolkurierShipmentService::getDeliveryServices()`. + - `createShipment($login, $token, $payload)` → `apimetod=create_order`. Payload zgodny z oficjalna doca PDF v1.11 (zweryfikowany): `shipmenttype` (lowercase: box/envelope/palette/small_parcel/parcel_size_20), `courier` (servicecode), `description`, `sender`/`recipient` (company/person/street/housenumber/flatnumber/postcode/city/email/phone/country/point_id), `packs[]` (length/width/height/weight/amount/type), `pickup` (pickupdate/pickuptimefrom/pickuptimeto/nocourierorder), opcjonalnie `COD` i `insurance`. + - `getLabel($login, $token, $orderno)` → `apimetod=get_label`. **API przyjmuje WYLACZNIE `orderno: Array`** (zweryfikowane w dokumentacji PDF). Rozmiar etykiety A4 vs A6 sterowany jest w panelu klienta polkurier.pl (Ustawienia konta → Preferencje etykiet), NIE przez API. Odpowiedz: `{file: }`. + - `getStatus($login, $token, $orderno)` → `apimetod=get_status`. Zwraca `{url, status_date, status, status_code, delivered_date}`. Kody w tabeli ORDER_STATUS (O/P/A/WP/D/Z/W). + - `cancelOrder($login, $token, $orderno)` → `apimetod=cancel_order`. Zwraca `{cancellation: true}`. Nie wywolywane przez nasz kod w Phase 128 (operator anuluje w panelu polkuriera). + - `getInpostParcelMachines` + `getCourierPoints` — stuby na przyszle rozszerzenie UI (panel paczkomatow). Aktualnie nie wykorzystywane w UI (operator wpisuje `receiver_point_id` recznie w sekcji Adres odbiorcy). +- Wspolny prywatny `call($apimetod, $data, $login, $token): mixed` parsuje envelope `{status, response}`. Sukces -> zwraca tresc `response`. Blad -> rzuca `RuntimeException` z trescia z `response` (string albo zserializowany JSON dla tablic). + +### PolkurierShipmentService (`src/Modules/Shipments/PolkurierShipmentService.php`) +- `final class implements ShipmentProviderInterface` (`code()='polkurier'`). DI: `PolkurierIntegrationRepository`, `PolkurierApiClient`, `ShipmentPackageRepository`, `CompanySettingsRepository`, `OrdersRepository`. +- `getDeliveryServices()` cache'uje per-request liste z `available_carriers`, normalizuje do `[{id, name, supports_pickup_point, point_courier, foreign_shipments, raw}]`. Pole `supports_pickup_point` to heurystyka po `servicecode`/`name` (paczkomat/parcel/inpost/orlen/pocztex/kurier48/punkt). +- `createShipment($orderId, $formData)` orchestruje pelny flow: + 1. Walidacja: order istnieje, `service_code`/`delivery_method_id` niepusty, credentials aktywne, sender ma street/city/postcode + name|company. + 2. Mapowanie `package_type` przez `normalizeShipmentType()` na zbior `[box, envelope, palette, small_parcel, parcel_size_20]` (lowercase, aliases dla PACKAGE/PARCEL/PACZKA/...). + 3. Buduje `recipient` z `order_addresses` (delivery → fallback customer) + override z formularza. Splituje ulice na `street`/`housenumber`/`flatnumber` regexem (`Marszalkowska 10/5` → street="Marszalkowska", house="10", flat="5"). + 4. `pickup` default: `nextBusinessDay()` + 10:00-16:00, `nocourierorder=false` (override mozliwy przez formularz: `pickup_date`, `pickup_time_from`, `pickup_time_to`, `no_courier_order`). + 5. `COD` jezeli `cod_amount > 0` (codtype='transfer', codbankaccount z `company_settings.bank_account` po stripowaniu nie-cyfr; throw `ShipmentException` jezeli pusty). + 6. INSERT do `shipment_packages` (provider='polkurier', status='pending', payload_json z pelnym requestem). + 7. Wywolanie `apiClient->createShipment()`. On success: parsing przez `extractOrderNumber()` (priorytet `number` → Order entity z SDK, fallback `orderno`/`order_no`/`order_number`/`order_id`/`id`, obsluga wrappera `{order:{...}}` i list) i `extractTrackingNumber()` (priorytet `waybills[0].number` → OrderWaybill entity, fallback top-level klucze). + 8. UPDATE `shipment_packages`: `status='created'`, `shipment_id=command_id=orderno`, `tracking_number`. + 9. Synchroniczna proba `downloadLabel()` (niekrytyczna — przy bledzie ignoruje, operator klikni "Pobierz" pozniej). + 10. Diagnostyka: gdy `orderno=''` mimo `status=success`, zapisuje fragment surowej odpowiedzi (400 znakow) do `shipment_packages.error_message` dla debug. +- `downloadLabel($packageId, $storagePath)`: wywoluje `get_label`, parsuje `extractLabelBase64()` (priorytet klucz `file` — zweryfikowane w SDK GetLabel.php, fallback `label`/`pdf`/`data`/`content`/`zpl`/`epl`), `base64_decode`, zapis do `storage/labels/polkurier_{packageId}_{orderno}.pdf`, UPDATE `label_path`. +- `checkCreationStatus($packageId)`: graceful — przy bledzie API zwraca zachowany `tracking_number` z DB (cron sam zaktualizuje przez TrackingService). + +### PolkurierTrackingService (`src/Modules/Shipments/PolkurierTrackingService.php`) +- `final class implements ShipmentTrackingInterface`. DI: `PolkurierApiClient`, `PolkurierIntegrationRepository`, `DeliveryStatusMappingRepository`. +- `supports('polkurier')`. `getDeliveryStatus($package)` woła `get_status` po `shipment_id`/`command_id` (orderno), parsuje `status_code` z `response` (z obsluga listy w response[0]). +- Mapowanie surowego `status_code` (O/P/A/WP/D/Z/W) → znormalizowany przez `DeliveryStatus::normalizeWithOverrides('polkurier', $rawStatus, $overrides)` z DB. Seed mapowan w migracji `20260514_000115`. +- Odporny na bledy: brak credentials → `null` (skip), wyjatek API → `null`, brak `status_code` → `null`. Cron nie crashuje. + +### DeliveryStatus::trackingUrl (Phase 128 patch) +- Carrier_id routing (DPD/UPS/GLS/InPost/Pocztex/...) dziala dla polkuriera automatycznie przez istniejacy `matchCarrierByName($encoded, $carrier)` (carrier_id ustawiany na servicecode z polkuriera, np. "INPOST", "DPD" — pasuje do substring matchu). +- Fallback dla provider='polkurier' bez carrier matchu: `https://polkurier.pl/sledz-paczke/`. + +### Wiring +- `routes/web.php`: `new PolkurierShipmentService(...)` zarejestrowany w `ShipmentProviderRegistry` obok Apaczki/InPost/AllegroWZA. +- `src/Modules/Cron/CronHandlerFactory.php`: `new PolkurierTrackingService(...)` w `ShipmentTrackingRegistry` w `shipment_tracking_sync` handler. +- `src/Modules/Shipments/ShipmentController.php`: `prepare()` fetchuje `polkurierServices` przez registry i przekazuje do widoku. `create()` rozszerzony o `service_code`/`pickup_date`/`pickup_time_from`/`pickup_time_to` przekazywane do `createShipment()`. + +### UI prepare panel (`resources/views/shipments/prepare.php`) +- Opcja "polkurier" w dropdownie `#shipment-carrier-select` (obok Allegro/InPost/Apaczka). +- `
` z dynamicznym `` synchronizowany z polkurier select przez `syncPolkurierFields()`. +- Brak dedykowanego selektora punktu odbioru — operator wpisuje `receiver_point_id` w istniejacy text input w sekcji Adres odbiorcy (np. `POP-RZE54`). Format string `POP-RZE54 | Lukasiewicza 78, 35-604 Rzeszow` z importu zamowienia nie jest parsowany — operator skraca recznie. +- JS toggle widocznosci paneli rozszerzony o polkurier; `clearHiddenFields()` czysci `service_code`; `showPanel('polkurier')` ustawia `provider_code='polkurier'`. + +### Rozmiar etykiety A4 vs A6 +- API polkurier nie udostepnia parametru sterowania rozmiarem etykiety w `get_label` ani `create_order` (zweryfikowane w PDF v1.11). +- Domyslny rozmiar ustawiany jest w **panelu klienta polkurier.pl → Ustawienia konta → Preferencje etykiet** (per-konto, globalnie dla wszystkich `get_label` calli). +- `polkurier_integration_settings.default_label_format` (PDF/ZPL/EPL) sluzy tylko typowi pliku, NIE rozmiarowi. + +### Seed delivery_status_mappings (`20260514_000115_seed_polkurier_delivery_status_mappings.sql`) +- 7 wpisow `provider='polkurier'` (kody z oficjalnej tabeli ORDER_STATUS w PDF v1.11): + - `O` → `created` (Oczekuje na platnosc) + - `P` → `confirmed` (Potwierdzone, list wygenerowany) + - `A` → `cancelled` (Anulowane) + - `WP` → `in_transit` (W przewozie) + - `D` → `delivered` (Dostarczona) + - `Z` → `returned` (Zwrot do nadawcy) + - `W` → `problem` (Wyjatek) +- Idempotentne: `ON DUPLICATE KEY UPDATE normalized_status / description / updated_at`. + +### Boundaries / co NIE zostalo zmienione +- Apaczka (`ApaczkaShipmentService`, `ApaczkaTrackingService`, `apaczka_integration_settings`) niezalezna, dziala obok polkuriera. +- `ShipmentProviderInterface` i `ShipmentTrackingInterface` kontrakty niezmienione. +- `getInpostParcelMachines`/`getCourierPoints` w API client zaimplementowane ale nieuzywane przez UI w Phase 128 (operator wpisuje punkt recznie). +- `cancelOrder` zaimplementowane w API client ale nie wywolywane z UI/cron — operator anuluje w panelu polkuriera. +- Brak presetow przesylek dla polkuriera (`shipment_presets.provider_code='polkurier'`) — kolejna faza. + ## Phase 121 - SMSPLANET Conversation + Notifications ### SmsConversationService (`src/Modules/Sms/SmsConversationService.php`) diff --git a/.paul/codebase/db_schema.md b/.paul/codebase/db_schema.md index bc6fb77..73cc472 100644 --- a/.paul/codebase/db_schema.md +++ b/.paul/codebase/db_schema.md @@ -459,6 +459,18 @@ Indexes: `shipment_packages_order_idx`, `shipment_packages_status_idx`, `shipmen UNIQUE: `(provider, raw_status)` +**Seedowane mapowania:** +- `provider='inpost'`, `provider='apaczka'`, `provider='allegro_wza'` — w `DeliveryStatus.php` (hardcoded fallback przez `DeliveryStatus::normalize($provider, $rawStatus)`). +- `provider='polkurier'` (Phase 128, migracja `20260514_000115_seed_polkurier_delivery_status_mappings.sql`): + - `O` → `created` (Oczekuje na platnosc) + - `P` → `confirmed` (Potwierdzone, list wygenerowany) + - `A` → `cancelled` (Anulowane) + - `WP` → `in_transit` (W przewozie) + - `D` → `delivered` (Dostarczona) + - `Z` → `returned` (Zwrot do nadawcy) + - `W` → `problem` (Wyjatek) + - Kody z oficjalnej tabeli `ORDER_STATUS` w dokumentacji API polkurier v1.11 (marzec 2026). + --- ## Integrations diff --git a/.paul/codebase/tech_changelog.md b/.paul/codebase/tech_changelog.md index e9e4067..9d6b054 100644 --- a/.paul/codebase/tech_changelog.md +++ b/.paul/codebase/tech_changelog.md @@ -1,5 +1,41 @@ # Technical Changelog +## 2026-05-14 - Phase 128 Plan 01: polkurier ShipmentService + Tracking + UI prepare + +**Co zrobiono:** +- `src/Modules/Settings/PolkurierApiClient.php` — pelen kontrakt API: `createShipment` (`apimetod=create_order`), `getLabel` (`get_label`), `getStatus` (`get_status`), `cancelOrder` (`cancel_order`), `getAvailableCarriers` (`available_carriers`), `getInpostParcelMachines` (`inpost_parcel_machines`), `getCourierPoints` (`get_courier_point`). Wspolny prywatny `call($apimetod, $data, $login, $token): mixed` parsuje envelope `{status, response}`; sukces -> zwraca `response`, blad -> rzuca `RuntimeException` z trescia z `response` (string albo zserializowany JSON dla tablic). Kontrakt zweryfikowany na oficjalnej dokumentacji PDF v1.11 (marzec 2026) — pobrana z `https://www.polkurier.pl/files/download/api_documentation_pdf`, zachowana w `.paul/phases/128-polkurier-shipment-service/polkurier-api-docs.txt`. +- `src/Modules/Shipments/PolkurierShipmentService.php` — `final class implements ShipmentProviderInterface` (`code()='polkurier'`). Pelen flow `createShipment($orderId, $formData)`: walidacja credentials/sender, `normalizeShipmentType()` mapuje `package_type` (PACKAGE/BOX/...) na zbior polkuriera `[box,envelope,palette,small_parcel,parcel_size_20]` (lowercase wymagane przez API — odkryte podczas live testu), `splitStreetAndNumber()` rozdziela ulice regexem na `street`/`housenumber`/`flatnumber`, `buildRecipient` z payload `order_addresses` + override z formularza, `buildPickup` z domyslnym `nextBusinessDay()` + 10:00-16:00, `COD` z bank account z `company_settings`. Po sukcesie API: `extractOrderNumber` (priorytet `number` z SDK Order entity), `extractTrackingNumber` (priorytet `waybills[0].number` z OrderWaybill entity), synchroniczna proba `downloadLabel`. Diagnostyka: gdy `orderno=''`, zapisuje fragment surowej odpowiedzi do `shipment_packages.error_message`. +- `src/Modules/Shipments/PolkurierTrackingService.php` — `final class implements ShipmentTrackingInterface`. `getDeliveryStatus($package)` woła `get_status`, parsuje `status_code`, mapuje przez `DeliveryStatus::normalizeWithOverrides('polkurier', ...)` z `delivery_status_mappings`. Graceful: null przy braku credentials/wyjatku API/braku `status_code`. +- `src/Modules/Shipments/DeliveryStatus.php` — fallback URL sledzenia dla `provider='polkurier'`: `https://polkurier.pl/sledz-paczke/`. Carrier_id routing (DPD/UPS/GLS/InPost/Pocztex) dziala automatycznie przez istniejacy `matchCarrierByName()` (carrier_id ustawiany na servicecode z polkuriera, np. "INPOST" lub "DPD" — substring match). +- `routes/web.php` — `new PolkurierShipmentService(...)` w `ShipmentProviderRegistry`. Import `App\Modules\Settings\PolkurierApiClient` + `App\Modules\Shipments\PolkurierShipmentService`. +- `src/Modules/Cron/CronHandlerFactory.php` — `new PolkurierTrackingService(new PolkurierApiClient(), new PolkurierIntegrationRepository($this->db, $this->integrationSecret), new DeliveryStatusMappingRepository($this->db))` w `ShipmentTrackingRegistry` w handlerze `shipment_tracking_sync`. +- `src/Modules/Shipments/ShipmentController.php` — `prepare()` fetchuje `polkurierServices` przez registry i przekazuje do widoku. `create()` rozszerzony o pola `service_code`/`pickup_date`/`pickup_time_from`/`pickup_time_to` przekazywane do `createShipment()`. +- `resources/views/shipments/prepare.php` — opcja "polkurier" w `#shipment-carrier-select`, panel `#shipment-polkurier-panel` z `` wypelniony `$polkurierServices` (carrier_id, name). + - Conditional pickup point picker (`) + + SELECT -- pole do wyboru wartoci z listy (np. HTML
Metoda z zamowienia: :
@@ -209,12 +211,45 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
+
+ +
Brak uslug polkurier (sprawdz konfiguracje w Ustawienia → Integracje → polkurier).
+ + +
Dla uslug paczkomatowych wpisz ID punktu w pole "Punkt odbioru" w sekcji Adres odbiorcy ponizej (np. POP-RZE54).
+ +
+
Wybierz przewoznika
- + +