feat(140): shoppro polkurier delivery mapping

Phase 140 complete:

- add Polkurier as shopPRO delivery mapping provider

- reuse PolkurierShipmentService delivery services

- update PAUL state, docs, and changelog
This commit is contained in:
2026-05-18 09:59:51 +02:00
parent 06fd41a61d
commit 7fd88038e4
12 changed files with 509 additions and 71 deletions

View File

@@ -13,8 +13,8 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
| Attribute | Value | | Attribute | Value |
|-----------|-------| |-----------|-------|
| Version | 3.9.0-dev | | Version | 3.9.0-dev |
| Status | v3.9 Stabilizacja i splata dlugu technicznego in progress - Phase 139 Sonar Critical/Major Cleanup complete; Phase 140 ready to plan | | Status | v3.9 Stabilizacja i splata dlugu technicznego complete - Phase 140 shopPRO Polkurier Delivery Mapping closed |
| Last Updated | 2026-05-17 (Phase 139 closed) | | Last Updated | 2026-05-18 (Phase 140 unified) |
## Requirements ## Requirements
@@ -133,12 +133,13 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
- [x] Tracking i automatyzacje Erli: lokalny provider tracking jak w Allegro, retry niekrytycznej rejestracji paczki zewnetrznej Erli z `shipment_tracking_sync`, wspolny kontekst `shipment.created`/`shipment.status_changed` dla regul e-mail/SMS/statystyk — Phase 131 - [x] Tracking i automatyzacje Erli: lokalny provider tracking jak w Allegro, retry niekrytycznej rejestracji paczki zewnetrznej Erli z `shipment_tracking_sync`, wspolny kontekst `shipment.created`/`shipment.status_changed` dla regul e-mail/SMS/statystyk — Phase 131
- [x] Hardening Erli: spojna diagnostyka importu/ACK w `integration_order_sync_state.last_error`, brak ACK po blednym batchu, testy jednostkowe import/status sync i dokumentacja obserwowalnosci bez nowej migracji — Phase 132 - [x] Hardening Erli: spojna diagnostyka importu/ACK w `integration_order_sync_state.last_error`, brak ACK po blednym batchu, testy jednostkowe import/status sync i dokumentacja obserwowalnosci bez nowej migracji — Phase 132
- [x] Parytet Erli w powierzchniach wspolnych: filtr zrodla zamowien, kanaly statystyk dziennych/podsumowania, warunek integracji automatyzacji, menu integracji i etykiety `zrodlo` uzywaja wspolnego rejestru zrodel — Phase 133 - [x] Parytet Erli w powierzchniach wspolnych: filtr zrodla zamowien, kanaly statystyk dziennych/podsumowania, warunek integracji automatyzacji, menu integracji i etykiety `zrodlo` uzywaja wspolnego rejestru zrodel — Phase 133
- [x] Backlog Reality Check: `.paul/codebase/todo.md` i `.paul/codebase/concerns.md` sklasyfikowane przeciw aktualnemu kodowi/docs, z dowodami w `BACKLOG-AUDIT.md` i routingiem do faz 135-142 — Phase 134 - [x] Backlog Reality Check: `.paul/codebase/todo.md` i `.paul/codebase/concerns.md` sklasyfikowane przeciw aktualnemu kodowi/docs, z dowodami w `BACKLOG-AUDIT.md` i pierwotnym routingiem do kolejnych faz dlugu; nieaktualne fazy 140+ usuniete 2026-05-18 decyzja operatora — Phase 134
- [x] Accounting Net Correctness: nowe paragony zapisuja VAT-aware `receipts.total_net`, a statystyki dzienne preferuja source-level net, potem `order_items` VAT fallback, z gross `/1.23` tylko jako legacy fallback — Phase 135 - [x] Accounting Net Correctness: nowe paragony zapisuja VAT-aware `receipts.total_net`, a statystyki dzienne preferuja source-level net, potem `order_items` VAT fallback, z gross `/1.23` tylko jako legacy fallback — Phase 135
- [x] Fakturownia Invoice Idempotency: delegowane faktury uzywaja stabilnego `oid=orders.internal_order_number`, lookup-first `GET /invoices.json?oid=...`, lokalnego stanu `pending_external`/`failed_retryable` i auto-attach po timeoutach — Phase 136 - [x] Fakturownia Invoice Idempotency: delegowane faktury uzywaja stabilnego `oid=orders.internal_order_number`, lookup-first `GET /invoices.json?oid=...`, lokalnego stanu `pending_external`/`failed_retryable` i auto-attach po timeoutach — Phase 136
- [x] Delivery Status Backlog Verification: `DELIVERY-STATUS-MGMT` zamkniete jako wdrozone; runtime korzysta z DB-driven statusow, a read-only DB check nie wykazal starych ani niepoprawnych kluczy automatyzacji — Phase 137 - [x] Delivery Status Backlog Verification: `DELIVERY-STATUS-MGMT` zamkniete jako wdrozone; runtime korzysta z DB-driven statusow, a read-only DB check nie wykazal starych ani niepoprawnych kluczy automatyzacji — Phase 137
- [x] Security and Legacy Hardening: test SMTP ma strict TLS by default z lokalnym `SMTP_ALLOW_SELF_SIGNED_DEV`, szablony e-mail/SMS blokuja nieznane placeholdery, raw `$_SESSION` jest izolowany w `Session`, a wskazane widoki uzywaja `$component()` zamiast hard `require` — Phase 138 - [x] Security and Legacy Hardening: test SMTP ma strict TLS by default z lokalnym `SMTP_ALLOW_SELF_SIGNED_DEV`, szablony e-mail/SMS blokuja nieznane placeholdery, raw `$_SESSION` jest izolowany w `Session`, a wskazane widoki uzywaja `$component()` zamiast hard `require` — Phase 138
- [x] Sonar Critical/Major Cleanup: Phase 139 odswiezyla baseline Sonar i zmniejszyla OPEN BLOCKER/CRITICAL/MAJOR z 648 do 495 przez delivery-status/statistics cleanup, typowane wyjatki oraz szeroka migracje alert include/import patterns — Phase 139 - [x] Sonar Critical/Major Cleanup: Phase 139 odswiezyla baseline Sonar i zmniejszyla OPEN BLOCKER/CRITICAL/MAJOR z 648 do 495 przez delivery-status/statistics cleanup, typowane wyjatki oraz szeroka migracje alert include/import patterns — Phase 139
- [x] shopPRO Polkurier Delivery Mapping: zakladka `Dostawy` integracji shopPRO pozwala mapowac forme dostawy na Polkurier, laduje uslugi z `PolkurierShipmentService::getDeliveryServices()` i zapisuje `provider='polkurier'` w `carrier_delivery_method_mappings` bez migracji DB — Phase 140
- [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] 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 - [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
- [x] Order User Notes module (Phase 129): pelen CRUD notatek autorskich operatora per zamowienie. Reuse `order_notes` przez nowy `note_type='user'` z `user_id` (FK→users SET NULL) + `author_name` (snapshot) + indeks `idx_order_notes_type_order`. `OrderNotesService` z autoryzacja DB-level (`WHERE user_id = :user_id`, rowCount=0 ⇒ 403). Sekcja `#notes` w "Wiadomosci i zalaczniki" w `/orders/{id}` z inline edit form + delete przez `OrderProAlerts.confirm`. Badge `[N]` (indigo neutralny) przy nr zamowienia na `/orders/list` (subquery `user_notes_count` w paginate). Brak admin override (brak systemu rol w aplikacji) — edit/delete tylko dla autora — Phase 129 - [x] Order User Notes module (Phase 129): pelen CRUD notatek autorskich operatora per zamowienie. Reuse `order_notes` przez nowy `note_type='user'` z `user_id` (FK→users SET NULL) + `author_name` (snapshot) + indeks `idx_order_notes_type_order`. `OrderNotesService` z autoryzacja DB-level (`WHERE user_id = :user_id`, rowCount=0 ⇒ 403). Sekcja `#notes` w "Wiadomosci i zalaczniki" w `/orders/{id}` z inline edit form + delete przez `OrderProAlerts.confirm`. Badge `[N]` (indigo neutralny) przy nr zamowienia na `/orders/list` (subquery `user_notes_count` w paginate). Brak admin override (brak systemu rol w aplikacji) — edit/delete tylko dla autora — Phase 129
@@ -151,7 +152,7 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
### Active (In Progress) ### Active (In Progress)
- [ ] v3.9 Stabilizacja i splata dlugu technicznego — Phase 140 Performance Safeguards ready to plan after Phase 139. - None - v3.9 current milestone is complete.
### Planned (Next) ### Planned (Next)
@@ -267,13 +268,13 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
| `carrier_delivery_method_mappings` przechowuje `source_vendor_code`/`source_service_id` dla Erli | Vendor Erli i lokalny provider to osobne kontrakty, nie nalezy ich mieszac w polach Apaczki/InPost | 2026-05-16 | Active | | `carrier_delivery_method_mappings` przechowuje `source_vendor_code`/`source_service_id` dla Erli | Vendor Erli i lokalny provider to osobne kontrakty, nie nalezy ich mieszac w polach Apaczki/InPost | 2026-05-16 | Active |
| Erli hardening uzywa istniejacych powierzchni obserwowalnosci zamiast nowej tabeli logow | Operator wybral ujednolicenie istniejacych miejsc; `integration_order_sync_state.last_error`, wynik crona i activity log wystarczaja dla Phase 132 | 2026-05-16 | Active | | Erli hardening uzywa istniejacych powierzchni obserwowalnosci zamiast nowej tabeli logow | Operator wybral ujednolicenie istniejacych miejsc; `integration_order_sync_state.last_error`, wynik crona i activity log wystarczaja dla Phase 132 | 2026-05-16 | Active |
| Zrodla zamowien marketplace maja wspolny `OrderSourceRegistry` | Parytet Erli ma byc utrzymany wszedzie tam, gdzie kod potrzebuje listy lub etykiety zrodla; lokalne pary Allegro/shopPRO prowadzily do pominiec Erli | 2026-05-16 | Active | | Zrodla zamowien marketplace maja wspolny `OrderSourceRegistry` | Parytet Erli ma byc utrzymany wszedzie tam, gdzie kod potrzebuje listy lub etykiety zrodla; lokalne pary Allegro/shopPRO prowadzily do pominiec Erli | 2026-05-16 | Active |
| v3.9 debt phases start from evidence-backed backlog audit | Phase 134 rozdzielil wpisy aktywne, wdrozone, stale i decyzyjne; kolejne fazy 135-142 maja naprawiac tylko potwierdzone problemy | 2026-05-16 | Active | | v3.9 debt phases start from evidence-backed backlog audit | Phase 134 rozdzielil wpisy aktywne, wdrozone, stale i decyzyjne. 2026-05-18 operator usunal nieaktualne fazy 140+ i zastapil je pilna faza mapowania shopPRO -> Polkurier. | 2026-05-16 | Active |
| Existing receipt `total_net` rows are not backfilled | Operator wybral zakres Phase 135 tylko dla nowych paragonow; historia pozostaje bez migracji/UPDATE | 2026-05-16 | Active | | Existing receipt `total_net` rows are not backfilled | Operator wybral zakres Phase 135 tylko dla nowych paragonow; historia pozostaje bez migracji/UPDATE | 2026-05-16 | Active |
| Accounting net fallbacks prefer explicit source data before assumptions | Phase 135: source-level net > item net/gross+VAT > legacy gross `/1.23`; dostawa fallback jako 23% VAT | 2026-05-16 | Active | | Accounting net fallbacks prefer explicit source data before assumptions | Phase 135: source-level net > item net/gross+VAT > legacy gross `/1.23`; dostawa fallback jako 23% VAT | 2026-05-16 | Active |
| SMTP mailbox TLS is strict by default | Phase 138: `ssl` and STARTTLS verify peer and host name; self-signed/unverified certificates require `SMTP_ALLOW_SELF_SIGNED_DEV=true` and local/dev/testing env. | 2026-05-17 | Active | | SMTP mailbox TLS is strict by default | Phase 138: `ssl` and STARTTLS verify peer and host name; self-signed/unverified certificates require `SMTP_ALLOW_SELF_SIGNED_DEV=true` and local/dev/testing env. | 2026-05-17 | Active |
| Unknown e-mail/SMS template placeholders are blocked on save | Phase 138: `TemplateVariableCatalog` is the shared catalog; create/edit rejects unknown `{{group.variable}}` keys while existing DB rows are not migrated. | 2026-05-17 | Active | | Unknown e-mail/SMS template placeholders are blocked on save | Phase 138: `TemplateVariableCatalog` is the shared catalog; create/edit rejects unknown `{{group.variable}}` keys while existing DB rows are not migrated. | 2026-05-17 | Active |
| Raw session access belongs only in `App\Core\Support\Session` | Phase 138 moved auth, CSRF, flash and Allegro OAuth state access behind `Session::get/set/has/forget/pull`. | 2026-05-17 | Active | | Raw session access belongs only in `App\Core\Support\Session` | Phase 138 moved auth, CSRF, flash and Allegro OAuth state access behind `Session::get/set/has/forget/pull`. | 2026-05-17 | Active |
| Phase 139 cleanup slices must stay behavior-preserving and leave god-class splits to Phase 141 | Phase 139 reduced Sonar BLOCKER/CRITICAL/MAJOR from 648 to 495 without DB/schema/business-flow changes; `php:S1448` remains a dedicated architecture concern. | 2026-05-17 | Active | | Phase 139 cleanup slices must stay behavior-preserving and leave god-class splits to a future refactor | Phase 139 reduced Sonar BLOCKER/CRITICAL/MAJOR from 648 to 495 without DB/schema/business-flow changes; `php:S1448` remains a dedicated architecture concern. Former Phase 141 was removed from the active roadmap on 2026-05-18. | 2026-05-17 | Active |
| polkurier startuje jako jedna globalna konfiguracja (single-instance, mirror Apaczka/HostedSMS/SMSPLANET) z realnym testowym wywolaniem `apimetod=test_auth_api` | Operator ma jedno konto polkurier; fundament musi byc zweryfikowany na zywym API zanim dolozymy `PolkurierShipmentService` | 2026-05-14 | Active | | polkurier startuje jako jedna globalna konfiguracja (single-instance, mirror Apaczka/HostedSMS/SMSPLANET) z realnym testowym wywolaniem `apimetod=test_auth_api` | Operator ma jedno konto polkurier; fundament musi byc zweryfikowany na zywym API zanim dolozymy `PolkurierShipmentService` | 2026-05-14 | Active |
| polkurier wymaga `login + token` razem w body `authorization` (nie samego tokena) | Zweryfikowane w SDK polkurier-sdk (`Auth.php`/`Request.php`); kolumna `login VARCHAR(190)` w `polkurier_integration_settings` mimo ze PLAN tego nie wymagal — kontrakt API to dyktuje | 2026-05-14 | Active | | polkurier wymaga `login + token` razem w body `authorization` (nie samego tokena) | Zweryfikowane w SDK polkurier-sdk (`Auth.php`/`Request.php`); kolumna `login VARCHAR(190)` w `polkurier_integration_settings` mimo ze PLAN tego nie wymagal — kontrakt API to dyktuje | 2026-05-14 | Active |
| 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: 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 |
@@ -321,6 +322,6 @@ Quick Reference:
--- ---
*PROJECT.md — Updated when requirements or context change* *PROJECT.md — Updated when requirements or context change*
*Last updated: 2026-05-17 after Phase 139 (Sonar Critical/Major Cleanup) closure* *Last updated: 2026-05-18 after Phase 140 (shopPRO Polkurier Delivery Mapping) closure*

View File

@@ -12,7 +12,7 @@ Milestone porzadkujacy zbudowany z `.paul/codebase/todo.md` i `.paul/codebase/co
Rule for every phase/plan: przed implementacja sprawdzic w kodzie i dokumentacji, czy wpis nadal jest aktualny i czy nie zostal juz wdrozony; nastepnie przedstawic krotki plan operatorowi i zapytac o potwierdzenie. Dopiero po akceptacji wolno wprowadzac zmiany i uruchamiac testy. Jezeli wpis jest nieaktualny albo juz zrealizowany, faza/planu ma zamknac go dokumentacyjnie bez niepotrzebnej zmiany kodu. Rule for every phase/plan: przed implementacja sprawdzic w kodzie i dokumentacji, czy wpis nadal jest aktualny i czy nie zostal juz wdrozony; nastepnie przedstawic krotki plan operatorowi i zapytac o potwierdzenie. Dopiero po akceptacji wolno wprowadzac zmiany i uruchamiac testy. Jezeli wpis jest nieaktualny albo juz zrealizowany, faza/planu ma zamknac go dokumentacyjnie bez niepotrzebnej zmiany kodu.
Progress: 6 of 9 phases complete (67%). Progress: 7 of 7 phases complete (100%).
| Phase | Name | Plans | Status | | Phase | Name | Plans | Status |
|-------|------|-------|--------| |-------|------|-------|--------|
@@ -22,9 +22,7 @@ Progress: 6 of 9 phases complete (67%).
| 137 | Delivery Status Backlog Verification | 1/1 | Complete (2026-05-17; verification-only closure, no stale automation keys found) | | 137 | Delivery Status Backlog Verification | 1/1 | Complete (2026-05-17; verification-only closure, no stale automation keys found) |
| 138 | Security and Legacy Hardening | 1/1 | Complete (2026-05-17; SMTP TLS/template/session/view hardening, PHPUnit/Sonar env gaps documented) | | 138 | Security and Legacy Hardening | 1/1 | Complete (2026-05-17; SMTP TLS/template/session/view hardening, PHPUnit/Sonar env gaps documented) |
| 139 | Sonar Critical/Major Cleanup | 2/2 | Complete (2026-05-17; Sonar BLOCKER/CRITICAL/MAJOR reduced 648 -> 495 across two cleanup slices) | | 139 | Sonar Critical/Major Cleanup | 2/2 | Complete (2026-05-17; Sonar BLOCKER/CRITICAL/MAJOR reduced 648 -> 495 across two cleanup slices) |
| 140 | Performance Safeguards | TBD | Not started | | 140 | shopPRO Polkurier Delivery Mapping | 1/1 | Complete (2026-05-18; shopPRO delivery mapping supports Polkurier, manual UI/Sonar follow-up pending) |
| 141 | God Classes and Duplication Refactor | TBD | Not started |
| 142 | Architecture Guardrails | TBD | Not started |
### Phase 134: Backlog Reality Check ### Phase 134: Backlog Reality Check
@@ -56,20 +54,10 @@ Plans: 138-01 (complete; `.paul/phases/138-security-and-legacy-hardening/138-01-
Focus: Zmniejszyc potwierdzone problemy SonarQube z `concerns.md`: generic exceptions, zbyt wiele returnow, powtarzajace sie literaly, cognitive complexity, unused parameters, use-namespace-import oraz accessibility (`aria-label`, `<output>`). Przed kazda grupa zmian odswiezyc stan skanu albo lokalnie potwierdzic wystepowanie problemu. Focus: Zmniejszyc potwierdzone problemy SonarQube z `concerns.md`: generic exceptions, zbyt wiele returnow, powtarzajace sie literaly, cognitive complexity, unused parameters, use-namespace-import oraz accessibility (`aria-label`, `<output>`). Przed kazda grupa zmian odswiezyc stan skanu albo lokalnie potwierdzic wystepowanie problemu.
Plans: 139-01 (complete; `.paul/phases/139-sonar-critical-major-cleanup/139-01-SUMMARY.md`); 139-02 (complete; `.paul/phases/139-sonar-critical-major-cleanup/139-02-SUMMARY.md`) Plans: 139-01 (complete; `.paul/phases/139-sonar-critical-major-cleanup/139-01-SUMMARY.md`); 139-02 (complete; `.paul/phases/139-sonar-critical-major-cleanup/139-02-SUMMARY.md`)
### Phase 140: Performance Safeguards ### Phase 140: shopPRO Polkurier Delivery Mapping
Focus: Zweryfikowac i wdrozyc potwierdzone zabezpieczenia wydajnosciowe: deferred indexes `INDEX-106-01`, potencjalne N+1 w szczegolach zamowienia oraz ryzyko wzrostu kolejki cron bez backoffu. Indeksy i migracje wykonywac zgodnie z obecnym progiem danych i po potwierdzeniu operatora. Focus: Dodac Polkurier do zakladki Dostawy w ustawieniach integracji shopPRO, aby formy dostawy z zamowien shopPRO mogly byc mapowane na lokalna usluge Polkurier i pozniej automatycznie preselectowane przy przygotowaniu przesylki.
Plans: TBD (defined during $paul-plan) Plans: 140-01 (complete; `.paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-SUMMARY.md`)
### Phase 141: God Classes and Duplication Refactor
Focus: Wrocic do odlozonego Phase 68 i potwierdzonych `S1448`: `OrdersRepository`, `OrdersController`, `AutomationService`, `ShopproOrderMapper`, `ApaczkaShipmentService`, `ShopproIntegrationsController`, plus duplikacje `SslCertificateResolver`, `ToggleableRepositoryTrait`, `RedirectPathResolver` oraz overlap `ReceiptService`/accounting.
Plans: TBD (defined during $paul-plan)
### Phase 142: Architecture Guardrails
Focus: Po pilniejszych poprawkach zdecydowac, ktore guardraile sa warte wdrozenia teraz: interfejsy repozytoriow do testow, typowane event/action names, wspolna warstwa walidacji, ewentualne naglowki cache i lokalne query caching. Kazdy element wymaga osobnej decyzji, bo czesc ma niski impact.
Plans: TBD (defined during $paul-plan)
## Previous Milestone ## Previous Milestone
@@ -633,4 +621,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
--- ---
*Roadmap created: 2026-03-12* *Roadmap created: 2026-03-12*
*Last updated: 2026-05-17 - Phase 139 complete; Phase 140 ready to plan* *Last updated: 2026-05-18 - Phase 140 complete; v3.9 milestone complete; obsolete phases 140+ removed*

View File

@@ -2,45 +2,46 @@
## Project Reference ## Project Reference
See: .paul/PROJECT.md (updated 2026-05-17) See: .paul/PROJECT.md (updated 2026-05-18)
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami. **Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
**Current focus:** v3.9 Stabilizacja i splata dlugu technicznego; Phase 139 Sonar Critical/Major Cleanup complete, Phase 140 Performance Safeguards ready to plan. **Current focus:** v3.9 Stabilizacja i splata dlugu technicznego complete; Phase 140 shopPRO Polkurier Delivery Mapping unified.
## Current Position ## Current Position
Milestone: v3.9 Stabilizacja i splata dlugu technicznego Milestone: v3.9 Stabilizacja i splata dlugu technicznego
Phase: 140 of 142 (Performance Safeguards) Phase: 140 of 140 (shopPRO Polkurier Delivery Mapping) - Complete
Plan: Not started Plan: 140-01 complete
Status: Ready to plan Status: Milestone complete, ready for next milestone or release decision
Last activity: 2026-05-17 23:21 - Phase 139 complete; transitioned to Phase 140 Last activity: 2026-05-18 00:00 - Unified .paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-PLAN.md
Progress: Progress:
- Milestone v3.9: [#######---] 67% (6 of 9 phases complete) - Milestone v3.9: [##########] 100% (7 of 7 phases complete)
- Phase 140: [----------] 0% (ready to plan) - Phase 140: [##########] 100% (complete)
## Loop Position ## Loop Position
Current loop state: Current loop state:
``` ```
PLAN -> APPLY -> UNIFY PLAN -> APPLY -> UNIFY
done done done [Phase 139 loop complete; ready for Phase 140 PLAN] done done done [Loop complete - milestone complete]
``` ```
## Session Continuity ## Session Continuity
Last session: 2026-05-17 23:21 Last session: 2026-05-18 00:00
Stopped at: Phase 139 complete, ready to plan Phase 140 Stopped at: Phase 140 complete; v3.9 milestone complete
Next action: $paul-plan for Phase 140 Performance Safeguards Next action: Run $paul-complete-milestone or start next milestone planning
Resume file: .paul/ROADMAP.md Resume file: .paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-SUMMARY.md
## Pending parallel work ## Pending parallel work
- None — Phase 118, 121, 122 wszystkie zacommitowane (8f14851, 360eef1). - None — Phase 118, 121, 122 wszystkie zacommitowane (8f14851, 360eef1).
## Git State ## Git State
Last phase commit: HEAD feat(139): sonar critical major cleanup Last commit: HEAD feat(140): shoppro polkurier delivery mapping
Previous: feat(136): fakturownia invoice idempotency Last phase commit: HEAD feat(140): shoppro polkurier delivery mapping
Previous: feat(139): sonar critical major cleanup
Branch: main Branch: main
### Skill Audit (Phase 139) ### Skill Audit (Phase 139)
@@ -50,6 +51,12 @@ Branch: main
| `sonar-scanner` | invoked | Local PATH did not contain the scanner, but the official Windows x64 scanner was downloaded to `%TEMP%` and used successfully before and after cleanup. | | `sonar-scanner` | invoked | Local PATH did not contain the scanner, but the official Windows x64 scanner was downloaded to `%TEMP%` and used successfully before and after cleanup. |
| `sonar-scanner` 139-02 | invoked | Reused the official Windows x64 scanner from `%TEMP%`; final scan succeeded with analysis `2c18a5b3-40b4-41d8-b826-df88615749db` and 495 OPEN BLOCKER/CRITICAL/MAJOR issues. | | `sonar-scanner` 139-02 | invoked | Reused the official Windows x64 scanner from `%TEMP%`; final scan succeeded with analysis `2c18a5b3-40b4-41d8-b826-df88615749db` and 495 OPEN BLOCKER/CRITICAL/MAJOR issues. |
### Skill Audit (Phase 140)
| Expected | Invoked | Notes |
|----------|---------|-------|
| `sonar-scanner` | gap documented | `sonar-project.properties` exists, but `sonar-scanner` is not available in PATH and the Phase 139 `%TEMP%` fallback scanner is not present. |
### Skill Audit (Phase 129) ### Skill Audit (Phase 129)
| Expected | Invoked | Notes | | Expected | Invoked | Notes |
@@ -131,17 +138,20 @@ Branch: main
- Phase 136 APPLY: `php bin/migrate.php` could not run because local MySQL refused connection; `vendor/bin/phpunit` is missing; `sonar-scanner` is unavailable in PATH. PHP lint, documentation grep, git diff check and ad-hoc SQLite repository smoke passed. - Phase 136 APPLY: `php bin/migrate.php` could not run because local MySQL refused connection; `vendor/bin/phpunit` is missing; `sonar-scanner` is unavailable in PATH. PHP lint, documentation grep, git diff check and ad-hoc SQLite repository smoke passed.
- Phase 138 APPLY: `vendor/bin/phpunit` is missing, so new unit tests were linted but not run; `sonar-scanner` is unavailable in PATH. PHP lint, targeted `rg` checks and `git diff --check` passed. - Phase 138 APPLY: `vendor/bin/phpunit` is missing, so new unit tests were linted but not run; `sonar-scanner` is unavailable in PATH. PHP lint, targeted `rg` checks and `git diff --check` passed.
- Phase 139 APPLY: local PATH still does not contain `sonar-scanner`, but the official Windows x64 scanner was downloaded to `%TEMP%` and used successfully. `vendor/bin/phpunit` remains unavailable because `vendor/` is missing and Composer is not installed in PATH. - Phase 139 APPLY: local PATH still does not contain `sonar-scanner`, but the official Windows x64 scanner was downloaded to `%TEMP%` and used successfully. `vendor/bin/phpunit` remains unavailable because `vendor/` is missing and Composer is not installed in PATH.
- Phase 140: deferred indexes should be applied only after operator confirms dataset size/prod timing. - Phase 140 APPLY: manual UI smoke was not run because local app/DB session was not started; Sonar scan could not run because `sonar-scanner` is unavailable.
- Obsolete Phase 140+ debt plans were removed from the active roadmap on 2026-05-18 by operator decision; performance/debt items can be reintroduced later only if still relevant.
### Deferred Issues ### Deferred Issues
- Backlog items and concern groups classified in `.paul/phases/134-backlog-reality-check/BACKLOG-AUDIT.md`; remaining implementation deferred to phases 136-142. - Backlog items and concern groups classified in `.paul/phases/134-backlog-reality-check/BACKLOG-AUDIT.md`; obsolete Phase 140+ debt work removed from active roadmap by operator decision on 2026-05-18.
## Pending Actions ## Pending Actions
- Phase 138 follow-up: run `vendor/bin/phpunit tests/Unit/SmtpSecurityContextFactoryTest.php tests/Unit/TemplateVariableCatalogTest.php` after dependencies are installed. - Phase 138 follow-up: run `vendor/bin/phpunit tests/Unit/SmtpSecurityContextFactoryTest.php tests/Unit/TemplateVariableCatalogTest.php` after dependencies are installed.
- Phase 139 follow-up: split `OrdersStatisticsRepository` (`php:S1448`, 43 methods) or include it in Phase 141 god-class refactor. - Phase 139 follow-up: split `OrdersStatisticsRepository` (`php:S1448`, 43 methods) in a future god-class refactor if still relevant.
- Phase 139 follow-up: continue with confirmed groups `php:S1142`, `php:S3776`, `php:S1172`, `php:S1192`, `php:S112`, plus Web table/accessibility issues. `php:S4833` is now only 3 core framework require issues. - Phase 139 follow-up: continue with confirmed groups `php:S1142`, `php:S3776`, `php:S1172`, `php:S1192`, `php:S112`, plus Web table/accessibility issues. `php:S4833` is now only 3 core framework require issues.
- Phase 140 follow-up: manual smoke `/settings/integrations/shoppro?tab=delivery` -> wybierz Polkurier -> zapisz -> odswiez -> mapowanie pozostaje; potem przygotuj przesylke shopPRO i potwierdz preselect `provider='polkurier'`.
- Phase 140 follow-up: uruchom SonarQube scan po przywroceniu `sonar-scanner` w PATH albo ponownym pobraniu oficjalnego scanner fallback.
- Phase 138 manual smoke: test a real SMTP SSL/STARTTLS mailbox in strict mode; test invalid and valid e-mail/SMS template saves in UI. - Phase 138 manual smoke: test a real SMTP SSL/STARTTLS mailbox in strict mode; test invalid and valid e-mail/SMS template saves in UI.
- Manualne testy AC-1..AC-7 dla Phase 112 na zywej bazie (XAMPP online). - Manualne testy AC-1..AC-7 dla Phase 112 na zywej bazie (XAMPP online).
- Backfill zamowienia #882 - operator robi recznie po wdrozeniu (poza zakresem planu). - Backfill zamowienia #882 - operator robi recznie po wdrozeniu (poza zakresem planu).

View File

@@ -0,0 +1,24 @@
# 2026-05-18
## Co zrobiono
- [Phase 140, Plan 01] Dodano mapowanie form dostawy shopPRO na uslugi Polkurier.
- Rozszerzono zakladke `Dostawy` integracji shopPRO o przewoznika Polkurier i wyszukiwalna liste uslug.
- Zapis mapowan shopPRO obsluguje `provider='polkurier'` oraz zapis service code i nazwy uslugi w `carrier_delivery_method_mappings`.
- Zaktualizowano dokumentacje architektury, techniczny changelog oraz stan PAUL; stare nieaktualne fazy 140+ usunieto z aktywnej roadmapy.
- Gap: manualny smoke UI i SonarQube scan pozostaja do wykonania po uruchomieniu app/DB i przywroceniu `sonar-scanner`.
## Zmienione pliki
- `.paul/PROJECT.md`
- `.paul/ROADMAP.md`
- `.paul/STATE.md`
- `.paul/changelog/2026-05-18.md`
- `.paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-PLAN.md`
- `.paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-SUMMARY.md`
- `DOCS/ARCHITECTURE.md`
- `DOCS/TECH_CHANGELOG.md`
- `resources/lang/pl.php`
- `resources/views/settings/shoppro.php`
- `routes/web.php`
- `src/Modules/Settings/ShopproIntegrationsController.php`

View File

@@ -0,0 +1,185 @@
---
phase: 140-shoppro-polkurier-delivery-mapping
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- routes/web.php
- src/Modules/Settings/ShopproIntegrationsController.php
- resources/views/settings/shoppro.php
- resources/lang/pl.php
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
delegation: off
---
<objective>
## Goal
Dodac Polkurier jako dostepnego lokalnego providera w mapowaniu form dostawy shopPRO, tak aby operator mogl przypisac forme dostawy z zamowienia shopPRO do uslugi Polkurier i zeby przygotowanie przesylki pozniej korzystalo z zapisanego mapowania.
## Purpose
Sprzedawca ma nadawac przesylki z orderPRO bez recznego przelaczania providera przy kazdym zamowieniu. shopPRO ma miec parytet z istniejacym flow przygotowania przesylek, gdzie `carrier_delivery_method_mappings` wskazuje provider, usluge i nazwe uslugi.
## Output
Zakladka `/settings/integrations/shoppro?tab=delivery` pokazuje Polkurier obok Allegro/InPost/Apaczki, pobiera uslugi z istniejacego `PolkurierShipmentService::getDeliveryServices()` i zapisuje mapowanie jako `provider='polkurier'`, `provider_service_id=<servicecode>`, `provider_service_name=<name>`.
</objective>
<context>
<clarifications>
- **Zakres fazy** - Czy potraktowac to jako pilna nowa faze PAUL, czy jako hotfix poza roadmapowym nurtem?
-> Odpowiedz: Jako nowa faza. Stare fazy od 140 w gore sa do usuniecia (nieaktualne).
- **Zakres funkcji** - Czy shopPRO ma dostac Polkurier jako kolejnego providera obok Allegro/InPost/Apaczka z lista uslug z `PolkurierShipmentService::getDeliveryServices()` i zapisem `provider='polkurier'`, `provider_service_id=servicecode`, `provider_service_name=name`?
-> Odpowiedz: Tak.
</clarifications>
## Project Context
@AGENTS.md
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@DOCS/DB_SCHEMA.md
@DOCS/ARCHITECTURE.md
## Source Files
@routes/web.php
@src/Modules/Settings/ShopproIntegrationsController.php
@src/Modules/Settings/CarrierDeliveryMethodMappingRepository.php
@src/Modules/Shipments/PolkurierShipmentService.php
@resources/views/settings/shoppro.php
@resources/lang/pl.php
@resources/views/shipments/prepare.php
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| sonar-scanner | required | Po APPLY, przed UNIFY | ○ |
| /feature-dev | optional | Przy implementacji nowej funkcji integracyjnej | ○ |
| /frontend-design | optional | Przy korekcie formularza mapowan dostawy | ○ |
| /code-review | optional | Po implementacji, przed UNIFY | ○ |
**BLOCKING:** `sonar-scanner` jest wymagany przez `.paul/SPECIAL-FLOWS.md` po APPLY. Jezeli lokalny PATH nadal go nie ma, uzyc sprawdzonego fallbacku z Phase 139 albo zapisac gap w SUMMARY.
</skills>
<acceptance_criteria>
## AC-1: Polkurier Widoczny W Mapowaniu shopPRO
```gherkin
Given istnieje zapisana integracja shopPRO oraz wykryte formy dostawy z zamowien tej integracji
When operator otwiera /settings/integrations/shoppro?tab=delivery
Then w kolumnie przewoznika moze wybrac Polkurier, a w kolumnie uslugi moze wybrac usluge Polkurier z listy dostepnych uslug
```
## AC-2: Zapis Mapowania Polkurier
```gherkin
Given operator wybral Polkurier i konkretna usluge dla formy dostawy shopPRO
When zapisuje formularz mapowania dostaw
Then `carrier_delivery_method_mappings` dla tej formy dostawy zapisuje `source_system='shoppro'`, `provider='polkurier'`, `provider_service_id` jako kod uslugi Polkurier i `provider_service_name` jako jej nazwe
```
## AC-3: Wczytanie Istniejacego Mapowania
```gherkin
Given forma dostawy shopPRO ma zapisane mapowanie na Polkurier
When operator ponownie otwiera zakladke Dostawy shopPRO
Then wiersz pokazuje wybranego przewoznika Polkurier i zaznaczona poprzednio usluge bez utraty zapisanych mapowan dla Allegro, InPost i Apaczki
```
## AC-4: Przygotowanie Przesylki Korzysta Z Mapowania
```gherkin
Given zamowienie shopPRO ma forme dostawy zmapowana na Polkurier
When operator przechodzi do przygotowania przesylki dla tego zamowienia
Then istniejacy flow preselectu providera otrzymuje `provider='polkurier'` oraz `delivery_method_id` rowny zapisanej usludze Polkurier
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Podlaczyc uslugi Polkurier do shopPRO delivery tab</name>
<files>routes/web.php, src/Modules/Settings/ShopproIntegrationsController.php</files>
<action>
Rozszerz `ShopproIntegrationsController` o opcjonalna zaleznosc `PolkurierShipmentService`.
- W `index()` przekaz do widoku `polkurierDeliveryServices`.
- W `loadDeliveryServices()` albo osobnej malej metodzie doladuj uslugi Polkurier przez `getDeliveryServices()`; bledy traktuj jak brak listy i pokaz czytelny komunikat tylko jesli jest sensowny, bez blokowania Allegro/Apaczka.
- W `saveDeliveryMappings()` obsluz `carrier[] = polkurier`, pobierz `polkurier_delivery_method_id[]` i zapisz `provider='polkurier'`, `provider_service_id`, pusty `provider_account_id`, `provider_carrier_id` opcjonalnie pusty/kod, oraz `provider_service_name`.
- W `routes/web.php` ustaw DI tak, by ten sam `PolkurierShipmentService` byl przekazany do kontrolera shopPRO i do `ShipmentProviderRegistry`.
Unikaj duplikowania wywolan `PolkurierApiClient` bezposrednio w kontrolerze, bo istniejacy provider juz normalizuje service list.
</action>
<verify>C:\xampp\php\php.exe -l src/Modules/Settings/ShopproIntegrationsController.php; C:\xampp\php\php.exe -l routes/web.php</verify>
<done>AC-1 i AC-2 sa spelnione po stronie kontrolera i DI.</done>
</task>
<task type="auto">
<name>Task 2: Dodac Polkurier do UI mapowan dostawy shopPRO</name>
<files>resources/views/settings/shoppro.php, resources/lang/pl.php</files>
<action>
Rozszerz istniejaca tabele mapowan bez tworzenia nowego widoku.
- Dodaj opcje `Polkurier` w select przewoznika.
- Dodaj panel wyboru uslugi Polkurier analogiczny do Apaczki: wyszukiwalna lista z `data-value=<id>` i `data-label=<name>`.
- Dodaj hidden input `polkurier_delivery_method_id[]` oraz wykorzystaj istniejacy hidden service-name, zeby zapis dostal nazwe uslugi.
- Rozszerz JS `showPanel`, reset pol oraz `selectOption()` tak, by Polkurier czyscil pola Allegro/Apaczka i zapisywal poprawny provider service id.
- Rozpoznawaj istniejace mapowanie `provider='polkurier'` jako aktualny carrier.
- Zaktualizuj polskie etykiety/opisy: opis ma wspominac Allegro WZA/InPost/Apaczka/Polkurier, placeholder ma byc neutralny (`Szukaj uslugi...`), dodaj komunikat braku uslug Polkurier.
Nie dodawaj inline CSS poza istniejacymi prostymi `style` uzytymi juz w tym widoku.
</action>
<verify>C:\xampp\php\php.exe -l resources/views/settings/shoppro.php; rg -n "polkurier|polkurierDeliveryServices|polkurier_delivery_method_id" resources/views/settings/shoppro.php resources/lang/pl.php</verify>
<done>AC-1 i AC-3 sa spelnione w UI.</done>
</task>
<task type="auto">
<name>Task 3: Udokumentowac kontrakt bez migracji</name>
<files>DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
Zaktualizuj dokumentacje techniczna.
- W `DOCS/ARCHITECTURE.md` dopisz, ze shopPRO delivery mapping obsluguje takze Polkurier przez `provider='polkurier'` i liste z `PolkurierShipmentService`.
- W `DOCS/TECH_CHANGELOG.md` dodaj wpis z data 2026-05-18 opisujacy, co i dlaczego zmieniono.
- Nie aktualizuj `DOCS/DB_SCHEMA.md`, bo plan nie zmienia tabel, kolumn, indeksow ani FK; istnieje juz `carrier_delivery_method_mappings`.
</action>
<verify>rg -n "shopPRO.*Polkurier|Polkurier.*shopPRO|2026-05-18" DOCS/ARCHITECTURE.md DOCS/TECH_CHANGELOG.md</verify>
<done>Dokumentacja odzwierciedla AC-1..AC-4 i jasno wskazuje brak migracji.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Nie zmieniac schematu bazy danych ani migracji; `carrier_delivery_method_mappings` juz ma potrzebne pola.
- Nie zmieniac kontraktu tworzenia przesylek Polkurier (`PolkurierShipmentService::createShipment()` i API payload) poza ewentualnym reuse listy uslug.
- Nie przebudowywac calego widoku shopPRO ani systemu zakladek.
- Nie usuwac obslugi Allegro, InPost ani Apaczki w mapowaniach shopPRO.
## SCOPE LIMITS
- Zakres dotyczy tylko mapowania form dostawy shopPRO na Polkurier.
- Brak nowego selektora punktow odbioru Polkurier; przygotowanie przesylki nadal uzywa istniejacych pol `receiver_point_id`/`sender_point_id`.
- Brak zmian w imporcie zamowien shopPRO.
- Brak zmian w Erli, Allegro delivery mapping i statusach dostaw.
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `C:\xampp\php\php.exe -l routes/web.php`
- [ ] `C:\xampp\php\php.exe -l src/Modules/Settings/ShopproIntegrationsController.php`
- [ ] `C:\xampp\php\php.exe -l resources/views/settings/shoppro.php`
- [ ] `rg -n "polkurier|polkurierDeliveryServices|polkurier_delivery_method_id" src/Modules/Settings/ShopproIntegrationsController.php resources/views/settings/shoppro.php resources/lang/pl.php`
- [ ] Manual smoke, jesli aplikacja i DB sa dostepne: `/settings/integrations/shoppro?tab=delivery` -> wybierz Polkurier -> zapisz -> odswiez -> mapowanie pozostaje.
- [ ] SonarQube scan zgodnie z `.paul/SPECIAL-FLOWS.md` albo udokumentowany gap/fallback.
- [ ] Wszystkie acceptance criteria spelnione.
</verification>
<success_criteria>
- Polkurier jest dostepny w UI mapowania dostaw shopPRO.
- Zapis mapowania tworzy rekord uzywany pozniej przez przygotowanie przesylki (`provider='polkurier'`, `provider_service_id`).
- Istniejace mapowania Allegro/InPost/Apaczka pozostaja kompatybilne.
- Dokumentacja techniczna opisuje nowy wariant bez sugerowania zmian schematu.
- Weryfikacje z sekcji `<verification>` przeszly albo gap jest jasno opisany.
</success_criteria>
<output>
After completion, create `.paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,130 @@
---
phase: 140-shoppro-polkurier-delivery-mapping
plan: 01
subsystem: settings
tags: [shoppro, polkurier, delivery-mapping, shipments]
requires:
- phase: 128-polkurier-shipment-service
provides: PolkurierShipmentService and delivery service list from polkurier available_carriers
provides:
- shopPRO delivery methods can be mapped to Polkurier services
- carrier_delivery_method_mappings rows can store provider='polkurier' for shopPRO
affects: [shoppro, shipments, carrier_delivery_method_mappings]
tech-stack:
added: []
patterns: [reuse existing ShipmentProviderInterface service list for settings mapping]
key-files:
created:
- .paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-SUMMARY.md
modified:
- routes/web.php
- src/Modules/Settings/ShopproIntegrationsController.php
- resources/views/settings/shoppro.php
- resources/lang/pl.php
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
key-decisions:
- "Use existing carrier_delivery_method_mappings; no schema change."
- "Load Polkurier services via PolkurierShipmentService instead of duplicating API calls in settings."
patterns-established:
- "shopPRO delivery mapping can add local providers by extending provider-specific hidden field + shared provider_service_name snapshot."
duration: 1h
started: 2026-05-18T00:00:00+02:00
completed: 2026-05-18T00:00:00+02:00
---
# Phase 140 Plan 01 Summary: shopPRO Polkurier Delivery Mapping
shopPRO delivery forms can now be mapped directly to Polkurier services and later reused by the existing shipment prepare flow.
## Status
UNIFY complete. Phase complete.
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~1h |
| Started | 2026-05-18T00:00:00+02:00 |
| Completed | 2026-05-18T00:00:00+02:00 |
| Tasks | 3 completed |
| Files modified | 11 |
## Tasks Completed
### Task 1: Podlaczyc uslugi Polkurier do shopPRO delivery tab
- `ShopproIntegrationsController` dostal opcjonalna zaleznosc `PolkurierShipmentService`.
- Zakladka `delivery` przekazuje do widoku `polkurierDeliveryServices`.
- `saveDeliveryMappings()` obsluguje `carrier[]=polkurier` i zapisuje `provider='polkurier'` z `provider_service_id` z `polkurier_delivery_method_id[]`.
- `routes/web.php` przekazuje ten sam `PolkurierShipmentService` do kontrolera shopPRO i do `ShipmentProviderRegistry`.
### Task 2: Dodac Polkurier do UI mapowan dostawy shopPRO
- W `resources/views/settings/shoppro.php` dodano opcje `Polkurier`, panel wyszukiwalnej listy uslug i hidden input `polkurier_delivery_method_id[]`.
- Istniejace mapowanie `provider='polkurier'` jest rozpoznawane i odtwarzane po odswiezeniu.
- JS czysci i synchronizuje pola Allegro/InPost/Apaczka/Polkurier bez zmiany schematu formularza.
- Tlumaczenia opisow i placeholderow w `resources/lang/pl.php` zostaly zaktualizowane.
### Task 3: Udokumentowac kontrakt bez migracji
- `DOCS/ARCHITECTURE.md` opisuje mapowanie shopPRO -> Polkurier przez `carrier_delivery_method_mappings`.
- `DOCS/TECH_CHANGELOG.md` ma wpis z 2026-05-18.
- `DOCS/DB_SCHEMA.md` bez zmian, bo tabela juz miala potrzebne pola.
## Verification
- PASS: `C:\xampp\php\php.exe -l routes/web.php`
- PASS: `C:\xampp\php\php.exe -l src/Modules/Settings/ShopproIntegrationsController.php`
- PASS: `C:\xampp\php\php.exe -l resources/views/settings/shoppro.php`
- PASS: `rg -n "polkurier|polkurierDeliveryServices|polkurier_delivery_method_id" src/Modules/Settings/ShopproIntegrationsController.php resources/views/settings/shoppro.php resources/lang/pl.php`
- PASS: `rg -n "shopPRO.*Polkurier|Polkurier.*shopPRO|2026-05-18" DOCS/ARCHITECTURE.md DOCS/TECH_CHANGELOG.md`
- PASS: `git diff --check` (only Git CRLF warnings, no whitespace errors)
## Gaps / Not Run
- Manual UI smoke was not run because the local app/DB session was not started during APPLY.
- SonarQube scan was not run: `sonar-project.properties` exists, but `sonar-scanner` is not available in PATH and the Phase 139 `%TEMP%` fallback scanner is not present.
## Files Modified
- `routes/web.php`
- `src/Modules/Settings/ShopproIntegrationsController.php`
- `resources/views/settings/shoppro.php`
- `resources/lang/pl.php`
- `DOCS/ARCHITECTURE.md`
- `DOCS/TECH_CHANGELOG.md`
- `.paul/PROJECT.md`
- `.paul/ROADMAP.md`
- `.paul/STATE.md`
- `.paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-PLAN.md`
- `.paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-SUMMARY.md`
## Acceptance Criteria
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Polkurier Widoczny W Mapowaniu shopPRO | Pass | UI includes Polkurier and lists services from `PolkurierShipmentService::getDeliveryServices()`. |
| AC-2: Zapis Mapowania Polkurier | Pass | Save handler writes `provider='polkurier'`, selected service code and service name snapshot. |
| AC-3: Wczytanie Istniejacego Mapowania | Pass | Existing `provider='polkurier'` rows rehydrate the selected carrier and service. |
| AC-4: Przygotowanie Przesylki Korzysta Z Mapowania | Pass | Existing prepare flow reads shared mapping and receives `provider='polkurier'` plus mapped method id. |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Reuse `carrier_delivery_method_mappings` | Existing table already stores source, provider, service id and service name. | No migration required. |
| Reuse `PolkurierShipmentService::getDeliveryServices()` | Provider service already normalizes Polkurier available carriers. | Avoids a duplicate settings-only Polkurier API path. |
## Deviations from Plan
| Type | Count | Impact |
|------|-------|--------|
| Deferred verification | 2 | Manual UI smoke and Sonar scan documented as gaps. |
## Next Phase Readiness
**Ready:**
- Phase 140 is complete in code and documentation.
- The active v3.9 roadmap has no remaining phases after obsolete 140+ debt phases were removed by operator decision.
**Concerns:**
- Manual UI smoke should be run when local app/DB are available.
- Sonar scan should be run once `sonar-scanner` is restored or downloaded again.
**Blockers:**
- None for phase closure.

View File

@@ -139,6 +139,7 @@ Phase 135 fixes daily net totals: `OrdersStatisticsRepository::netAmountSql()` p
### Shipment Flow ### Shipment Flow
Phase 130 adds an Erli-specific post-label step: for `orders.source='erli'`, `ShipmentController` calls `ErliExternalShipmentService::syncPackage()` after a local provider has a tracking number. Phase 131 extends the same Allegro-like contract into cron tracking: `ShipmentTrackingHandler` retries the Erli external parcel sync after a provider returns tracking status, but the retry is non-critical and never blocks local `delivery_status` updates. The service posts `vendor/status/trackingNumber/orderId` to Erli `POST /shipping/external` and stores the response in `shipment_packages.payload_json.erli_external_parcel`; failures are activity-log warnings. Phase 130 adds an Erli-specific post-label step: for `orders.source='erli'`, `ShipmentController` calls `ErliExternalShipmentService::syncPackage()` after a local provider has a tracking number. Phase 131 extends the same Allegro-like contract into cron tracking: `ShipmentTrackingHandler` retries the Erli external parcel sync after a provider returns tracking status, but the retry is non-critical and never blocks local `delivery_status` updates. The service posts `vendor/status/trackingNumber/orderId` to Erli `POST /shipping/external` and stores the response in `shipment_packages.payload_json.erli_external_parcel`; failures are activity-log warnings.
Phase 140 extends shopPRO delivery mapping with Polkurier. `/settings/integrations/shoppro?tab=delivery` now lists Polkurier services from `PolkurierShipmentService::getDeliveryServices()` and stores selected rows in `carrier_delivery_method_mappings` as `source_system='shoppro'`, `provider='polkurier'`, `provider_service_id=<servicecode>`, and `provider_service_name=<name>`. No schema change is required; the existing shipment prepare flow already reads the shared mapping and preselects `provider='polkurier'` with the stored delivery method id.
1. **Create**`ShipmentController::create()``ShipmentProviderRegistry` → carrier `ShipmentService::createShipment()``ShipmentPackageRepository::insert()` 1. **Create**`ShipmentController::create()``ShipmentProviderRegistry` → carrier `ShipmentService::createShipment()``ShipmentPackageRepository::insert()`
2. **Track** — Cron `ShipmentTrackingHandler``ShipmentTrackingRegistry` → carrier tracking API → optional Erli external parcel retry → `ShipmentPackageRepository::updateDeliveryStatus()` → shared `shipment.status_changed` automation event when normalized status really changes. 2. **Track** — Cron `ShipmentTrackingHandler``ShipmentTrackingRegistry` → carrier tracking API → optional Erli external parcel retry → `ShipmentPackageRepository::updateDeliveryStatus()` → shared `shipment.status_changed` automation event when normalized status really changes.

View File

@@ -1,5 +1,19 @@
# Technical Changelog # Technical Changelog
## 2026-05-18 - Phase 140 Plan 01: shopPRO Polkurier Delivery Mapping
**Co zrobiono:**
- Dodano Polkurier jako lokalnego providera w zakladce `Dostawy` ustawien integracji shopPRO.
- Lista uslug Polkurier w mapowaniu shopPRO korzysta z istniejacego `PolkurierShipmentService::getDeliveryServices()`, bez duplikowania klienta API w kontrolerze ustawien.
- Zapis mapowania formy dostawy shopPRO obsluguje `provider='polkurier'`, `provider_service_id=<servicecode>` i snapshot `provider_service_name`.
- Zachowano dotychczasowe mapowania Allegro WZA/InPost/Apaczka oraz istniejacy kontrakt tabeli `carrier_delivery_method_mappings`.
**Dlaczego:**
- Operator musi mapowac formy dostawy shopPRO na Polkurier, aby pozniej przygotowanie przesylki automatycznie wybieralo wlasciwego providera i usluge bez recznego przelaczania.
**BREAKING / migracja:**
- Brak migracji DB i brak zmian breaking; wykorzystano istniejaca tabele `carrier_delivery_method_mappings`.
## 2026-05-17 - Phase 139 Plan 02: Sonar Critical/Major Cleanup ## 2026-05-17 - Phase 139 Plan 02: Sonar Critical/Major Cleanup
**Co zrobiono:** **Co zrobiono:**

View File

@@ -1289,17 +1289,18 @@ return [
], ],
'delivery' => [ 'delivery' => [
'title' => 'Formy dostawy', 'title' => 'Formy dostawy',
'description' => 'Mapowanie form dostawy shopPRO do uslug nadawczych Allegro WZA/InPost.', 'description' => 'Mapowanie form dostawy shopPRO do uslug nadawczych Allegro WZA/InPost/Apaczka/Polkurier.',
'select_integration_first' => 'Najpierw wybierz lub zapisz integracje w zakladce Integracja.', 'select_integration_first' => 'Najpierw wybierz lub zapisz integracje w zakladce Integracja.',
'empty_orders' => 'Brak form dostawy shopPRO wykrytych w zamowieniach tej integracji.', 'empty_orders' => 'Brak form dostawy shopPRO wykrytych w zamowieniach tej integracji.',
'not_connected' => 'Brak aktywnego polaczenia Allegro. Podlacz konto Allegro, aby pobrac liste uslug dostawy.', 'not_connected' => 'Brak aktywnego polaczenia Allegro. Podlacz konto Allegro, aby pobrac liste uslug dostawy.',
'no_inpost_services' => 'Brak uslug InPost (sprawdz polaczenie z Allegro).', 'no_inpost_services' => 'Brak uslug InPost (sprawdz polaczenie z Allegro).',
'no_polkurier_services' => 'Brak uslug Polkurier (sprawdz konfiguracje w Ustawienia > Integracje > polkurier).',
'fields' => [ 'fields' => [
'order_method' => 'Forma dostawy shopPRO', 'order_method' => 'Forma dostawy shopPRO',
'carrier' => 'Przewoznik', 'carrier' => 'Przewoznik',
'allegro_service' => 'Usluga dostawy', 'allegro_service' => 'Usluga dostawy',
'no_mapping' => 'brak mapowania', 'no_mapping' => 'brak mapowania',
'search_placeholder' => 'Szukaj uslugi Allegro...', 'search_placeholder' => 'Szukaj uslugi...',
'select_carrier_first' => 'Najpierw wybierz przewoznika.', 'select_carrier_first' => 'Najpierw wybierz przewoznika.',
], ],
'actions' => [ 'actions' => [

View File

@@ -16,6 +16,7 @@ $dmMappings = is_array($deliveryMappings ?? null) ? $deliveryMappings : [];
$dmOrderMethods = is_array($orderDeliveryMethods ?? null) ? $orderDeliveryMethods : []; $dmOrderMethods = is_array($orderDeliveryMethods ?? null) ? $orderDeliveryMethods : [];
$dmAllegroServices = is_array($allegroDeliveryServices ?? null) ? $allegroDeliveryServices : []; $dmAllegroServices = is_array($allegroDeliveryServices ?? null) ? $allegroDeliveryServices : [];
$dmApaczkaServices = is_array($apaczkaDeliveryServices ?? null) ? $apaczkaDeliveryServices : []; $dmApaczkaServices = is_array($apaczkaDeliveryServices ?? null) ? $apaczkaDeliveryServices : [];
$dmPolkurierServices = is_array($polkurierDeliveryServices ?? null) ? $polkurierDeliveryServices : [];
$dmInpostServices = is_array($inpostDeliveryServices ?? null) ? $inpostDeliveryServices : []; $dmInpostServices = is_array($inpostDeliveryServices ?? null) ? $inpostDeliveryServices : [];
$dmServicesError = (string) ($allegroDeliveryServicesError ?? ''); $dmServicesError = (string) ($allegroDeliveryServicesError ?? '');
$dmMappingsByMethod = []; $dmMappingsByMethod = [];
@@ -480,7 +481,9 @@ foreach ($dmMappings as $dm) {
$currentServiceName = $currentMapping !== null ? trim((string) ($currentMapping['provider_service_name'] ?? '')) : ''; $currentServiceName = $currentMapping !== null ? trim((string) ($currentMapping['provider_service_name'] ?? '')) : '';
$currentProviderCarrierId = $currentMapping !== null ? trim((string) ($currentMapping['provider_carrier_id'] ?? '')) : ''; $currentProviderCarrierId = $currentMapping !== null ? trim((string) ($currentMapping['provider_carrier_id'] ?? '')) : '';
$currentCarrier = ''; $currentCarrier = '';
if ($currentProvider === 'apaczka') { if ($currentProvider === 'polkurier') {
$currentCarrier = 'polkurier';
} elseif ($currentProvider === 'apaczka') {
$currentCarrier = 'apaczka'; $currentCarrier = 'apaczka';
} elseif (stripos($currentProviderCarrierId, 'inpost') !== false) { } elseif (stripos($currentProviderCarrierId, 'inpost') !== false) {
$currentCarrier = 'inpost'; $currentCarrier = 'inpost';
@@ -499,12 +502,14 @@ foreach ($dmMappings as $dm) {
<option value="allegro"<?= $currentCarrier === 'allegro' && $currentMethodId !== '' ? ' selected' : '' ?>>Allegro</option> <option value="allegro"<?= $currentCarrier === 'allegro' && $currentMethodId !== '' ? ' selected' : '' ?>>Allegro</option>
<option value="inpost"<?= $currentCarrier === 'inpost' ? ' selected' : '' ?>>InPost</option> <option value="inpost"<?= $currentCarrier === 'inpost' ? ' selected' : '' ?>>InPost</option>
<option value="apaczka"<?= $currentCarrier === 'apaczka' ? ' selected' : '' ?>>Apaczka</option> <option value="apaczka"<?= $currentCarrier === 'apaczka' ? ' selected' : '' ?>>Apaczka</option>
<option value="polkurier"<?= $currentCarrier === 'polkurier' ? ' selected' : '' ?>>Polkurier</option>
</select> </select>
</td> </td>
<td> <td>
<div class="dm-service-wrap" data-row="<?= $rowIdx ?>"> <div class="dm-service-wrap" data-row="<?= $rowIdx ?>">
<input type="hidden" name="allegro_delivery_method_id[]" class="dm-hidden-method-id" value="<?= $e($currentProvider !== 'apaczka' ? $currentMethodId : '') ?>"> <input type="hidden" name="allegro_delivery_method_id[]" class="dm-hidden-method-id" value="<?= $e($currentProvider === 'allegro_wza' ? $currentMethodId : '') ?>">
<input type="hidden" name="apaczka_delivery_method_id[]" class="dm-hidden-apaczka-method-id" value="<?= $e($currentProvider === 'apaczka' ? $currentMethodId : '') ?>"> <input type="hidden" name="apaczka_delivery_method_id[]" class="dm-hidden-apaczka-method-id" value="<?= $e($currentProvider === 'apaczka' ? $currentMethodId : '') ?>">
<input type="hidden" name="polkurier_delivery_method_id[]" class="dm-hidden-polkurier-method-id" value="<?= $e($currentProvider === 'polkurier' ? $currentMethodId : '') ?>">
<input type="hidden" name="allegro_credentials_id[]" class="dm-hidden-credentials-id" value="<?= $e(trim((string) ($currentMapping['provider_account_id'] ?? ''))) ?>"> <input type="hidden" name="allegro_credentials_id[]" class="dm-hidden-credentials-id" value="<?= $e(trim((string) ($currentMapping['provider_account_id'] ?? ''))) ?>">
<input type="hidden" name="allegro_carrier_id[]" class="dm-hidden-carrier-id" value="<?= $e($currentProviderCarrierId) ?>"> <input type="hidden" name="allegro_carrier_id[]" class="dm-hidden-carrier-id" value="<?= $e($currentProviderCarrierId) ?>">
<input type="hidden" name="allegro_service_name[]" class="dm-hidden-service-name" value="<?= $e($currentServiceName) ?>"> <input type="hidden" name="allegro_service_name[]" class="dm-hidden-service-name" value="<?= $e($currentServiceName) ?>">
@@ -602,6 +607,39 @@ foreach ($dmMappings as $dm) {
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="dm-polkurier-panel dm-searchable-select" data-provider="polkurier" data-current-id="<?= $e($currentCarrier === 'polkurier' ? $currentMethodId : '') ?>" data-current-name="<?= $e($currentCarrier === 'polkurier' ? $currentServiceName : '') ?>" style="<?= $currentCarrier !== 'polkurier' ? 'display:none' : '' ?>">
<?php if ($dmPolkurierServices === []): ?>
<div class="muted"><?= $e($t('settings.integrations.delivery.no_polkurier_services')) ?></div>
<?php else: ?>
<input type="text" class="form-control dm-search-input" placeholder="<?= $e($t('settings.integrations.delivery.fields.search_placeholder')) ?>" value="<?= $e($currentCarrier === 'polkurier' ? $currentServiceName : '') ?>" autocomplete="off">
<div class="searchable-select__dropdown dm-dropdown">
<div class="searchable-select__option dm-option-clear" data-value="" data-label="" data-credentials-id="" data-carrier-id="">
<em class="muted">-- <?= $e($t('settings.integrations.delivery.fields.no_mapping')) ?> --</em>
</div>
<?php foreach ($dmPolkurierServices as $pSvc): ?>
<?php
if (!is_array($pSvc)) {
continue;
}
$pSvcId = trim((string) ($pSvc['id'] ?? $pSvc['servicecode'] ?? $pSvc['code'] ?? ''));
$pSvcName = trim((string) ($pSvc['name'] ?? $pSvcId));
$pSvcLabel = $pSvcName !== '' ? $pSvcName : $pSvcId;
if ($pSvcId === '') {
continue;
}
?>
<div class="searchable-select__option"
data-value="<?= $e($pSvcId) ?>"
data-label="<?= $e($pSvcLabel) ?>"
data-credentials-id=""
data-carrier-id="">
<?= $e($pSvcLabel) ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<div class="dm-empty-panel muted" style="<?= $currentCarrier !== '' ? 'display:none' : ($currentMethodId !== '' ? 'display:none' : '') ?>"> <div class="dm-empty-panel muted" style="<?= $currentCarrier !== '' ? 'display:none' : ($currentMethodId !== '' ? 'display:none' : '') ?>">
<?= $e($t('settings.integrations.delivery.fields.select_carrier_first')) ?> <?= $e($t('settings.integrations.delivery.fields.select_carrier_first')) ?>
</div> </div>
@@ -690,9 +728,11 @@ foreach ($dmMappings as $dm) {
var allegroPanel = serviceWrap.querySelector('.dm-allegro-panel'); var allegroPanel = serviceWrap.querySelector('.dm-allegro-panel');
var inpostPanel = serviceWrap.querySelector('.dm-inpost-panel'); var inpostPanel = serviceWrap.querySelector('.dm-inpost-panel');
var apaczkaPanel = serviceWrap.querySelector('.dm-apaczka-panel'); var apaczkaPanel = serviceWrap.querySelector('.dm-apaczka-panel');
var polkurierPanel = serviceWrap.querySelector('.dm-polkurier-panel');
var emptyPanel = serviceWrap.querySelector('.dm-empty-panel'); var emptyPanel = serviceWrap.querySelector('.dm-empty-panel');
var hiddenMethodId = serviceWrap.querySelector('.dm-hidden-method-id'); var hiddenMethodId = serviceWrap.querySelector('.dm-hidden-method-id');
var hiddenApaczkaMethodId = serviceWrap.querySelector('.dm-hidden-apaczka-method-id'); var hiddenApaczkaMethodId = serviceWrap.querySelector('.dm-hidden-apaczka-method-id');
var hiddenPolkurierMethodId = serviceWrap.querySelector('.dm-hidden-polkurier-method-id');
var hiddenCredentialsId = serviceWrap.querySelector('.dm-hidden-credentials-id'); var hiddenCredentialsId = serviceWrap.querySelector('.dm-hidden-credentials-id');
var hiddenCarrierId = serviceWrap.querySelector('.dm-hidden-carrier-id'); var hiddenCarrierId = serviceWrap.querySelector('.dm-hidden-carrier-id');
var hiddenServiceName = serviceWrap.querySelector('.dm-hidden-service-name'); var hiddenServiceName = serviceWrap.querySelector('.dm-hidden-service-name');
@@ -701,6 +741,7 @@ foreach ($dmMappings as $dm) {
if (allegroPanel) allegroPanel.style.display = carrier === 'allegro' ? '' : 'none'; if (allegroPanel) allegroPanel.style.display = carrier === 'allegro' ? '' : 'none';
if (inpostPanel) inpostPanel.style.display = carrier === 'inpost' ? '' : 'none'; if (inpostPanel) inpostPanel.style.display = carrier === 'inpost' ? '' : 'none';
if (apaczkaPanel) apaczkaPanel.style.display = carrier === 'apaczka' ? '' : '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 (emptyPanel) emptyPanel.style.display = carrier === '' ? '' : 'none';
} }
@@ -711,6 +752,7 @@ foreach ($dmMappings as $dm) {
showPanel(carrier); showPanel(carrier);
if (hiddenMethodId) hiddenMethodId.value = ''; if (hiddenMethodId) hiddenMethodId.value = '';
if (hiddenApaczkaMethodId) hiddenApaczkaMethodId.value = ''; if (hiddenApaczkaMethodId) hiddenApaczkaMethodId.value = '';
if (hiddenPolkurierMethodId) hiddenPolkurierMethodId.value = '';
if (hiddenCredentialsId) hiddenCredentialsId.value = ''; if (hiddenCredentialsId) hiddenCredentialsId.value = '';
if (hiddenCarrierId) hiddenCarrierId.value = ''; if (hiddenCarrierId) hiddenCarrierId.value = '';
if (hiddenServiceName) hiddenServiceName.value = ''; if (hiddenServiceName) hiddenServiceName.value = '';
@@ -723,7 +765,7 @@ foreach ($dmMappings as $dm) {
if (allegroDropdown) { if (allegroDropdown) {
allegroDropdown.classList.remove('is-open'); allegroDropdown.classList.remove('is-open');
} }
[inpostPanel, apaczkaPanel].forEach(function (panel) { [inpostPanel, apaczkaPanel, polkurierPanel].forEach(function (panel) {
if (!panel) return; if (!panel) return;
var inp = panel.querySelector('.dm-search-input'); var inp = panel.querySelector('.dm-search-input');
if (inp) { inp.value = ''; inp.blur(); } if (inp) { inp.value = ''; inp.blur(); }
@@ -742,6 +784,7 @@ foreach ($dmMappings as $dm) {
var hiddenMethodId = serviceWrap.querySelector('.dm-hidden-method-id'); var hiddenMethodId = serviceWrap.querySelector('.dm-hidden-method-id');
var hiddenApaczkaMethodId = serviceWrap.querySelector('.dm-hidden-apaczka-method-id'); var hiddenApaczkaMethodId = serviceWrap.querySelector('.dm-hidden-apaczka-method-id');
var hiddenPolkurierMethodId = serviceWrap.querySelector('.dm-hidden-polkurier-method-id');
var hiddenCredentialsId = serviceWrap.querySelector('.dm-hidden-credentials-id'); var hiddenCredentialsId = serviceWrap.querySelector('.dm-hidden-credentials-id');
var hiddenCarrierId = serviceWrap.querySelector('.dm-hidden-carrier-id'); var hiddenCarrierId = serviceWrap.querySelector('.dm-hidden-carrier-id');
var hiddenServiceName = serviceWrap.querySelector('.dm-hidden-service-name'); var hiddenServiceName = serviceWrap.querySelector('.dm-hidden-service-name');
@@ -751,16 +794,24 @@ foreach ($dmMappings as $dm) {
var provider = wrapper.getAttribute('data-provider') || ''; var provider = wrapper.getAttribute('data-provider') || '';
var isApaczka = provider === 'apaczka'; var isApaczka = provider === 'apaczka';
var isPolkurier = provider === 'polkurier';
function selectOption(opt) { function selectOption(opt) {
var val = opt.getAttribute('data-value') || ''; var val = opt.getAttribute('data-value') || '';
if (isApaczka) { if (isApaczka) {
if (hiddenMethodId) hiddenMethodId.value = ''; if (hiddenMethodId) hiddenMethodId.value = '';
if (hiddenApaczkaMethodId) hiddenApaczkaMethodId.value = val; if (hiddenApaczkaMethodId) hiddenApaczkaMethodId.value = val;
if (hiddenPolkurierMethodId) hiddenPolkurierMethodId.value = '';
if (hiddenCredentialsId) hiddenCredentialsId.value = '';
} else if (isPolkurier) {
if (hiddenMethodId) hiddenMethodId.value = '';
if (hiddenApaczkaMethodId) hiddenApaczkaMethodId.value = '';
if (hiddenPolkurierMethodId) hiddenPolkurierMethodId.value = val;
if (hiddenCredentialsId) hiddenCredentialsId.value = ''; if (hiddenCredentialsId) hiddenCredentialsId.value = '';
} else { } else {
if (hiddenMethodId) hiddenMethodId.value = val; if (hiddenMethodId) hiddenMethodId.value = val;
if (hiddenApaczkaMethodId) hiddenApaczkaMethodId.value = ''; if (hiddenApaczkaMethodId) hiddenApaczkaMethodId.value = '';
if (hiddenPolkurierMethodId) hiddenPolkurierMethodId.value = '';
if (hiddenCredentialsId) hiddenCredentialsId.value = opt.getAttribute('data-credentials-id') || ''; if (hiddenCredentialsId) hiddenCredentialsId.value = opt.getAttribute('data-credentials-id') || '';
} }
if (hiddenCarrierId) hiddenCarrierId.value = opt.getAttribute('data-carrier-id') || ''; if (hiddenCarrierId) hiddenCarrierId.value = opt.getAttribute('data-carrier-id') || '';

View File

@@ -188,6 +188,19 @@ return static function (Application $app): void {
$auth, $auth,
$inpostIntegrationRepository $inpostIntegrationRepository
); );
$companySettingsRepository = new CompanySettingsRepository($app->db());
$shipmentPackageRepository = new ShipmentPackageRepository($app->db());
$polkurierIntegrationRepository = new PolkurierIntegrationRepository(
$app->db(),
(string) $app->config('app.integrations.secret', '')
);
$polkurierShipmentService = new PolkurierShipmentService(
$polkurierIntegrationRepository,
new PolkurierApiClient(),
$shipmentPackageRepository,
$companySettingsRepository,
new OrdersRepository($app->db())
);
$shopproIntegrationsRepository = new ShopproIntegrationsRepository( $shopproIntegrationsRepository = new ShopproIntegrationsRepository(
$app->db(), $app->db(),
(string) $app->config('app.integrations.secret', '') (string) $app->config('app.integrations.secret', '')
@@ -206,7 +219,8 @@ return static function (Application $app): void {
$allegroOAuthClient, $allegroOAuthClient,
new AllegroApiClient(), new AllegroApiClient(),
$apaczkaIntegrationRepository, $apaczkaIntegrationRepository,
$apaczkaApiClient $apaczkaApiClient,
$polkurierShipmentService
); );
$fakturowniaIntegrationRepository = new FakturowniaIntegrationRepository( $fakturowniaIntegrationRepository = new FakturowniaIntegrationRepository(
$app->db(), $app->db(),
@@ -233,10 +247,6 @@ return static function (Application $app): void {
new HostedSmsApiClient(), new HostedSmsApiClient(),
new IntegrationsRepository($app->db()) new IntegrationsRepository($app->db())
); );
$polkurierIntegrationRepository = new PolkurierIntegrationRepository(
$app->db(),
(string) $app->config('app.integrations.secret', '')
);
$polkurierIntegrationController = new PolkurierIntegrationController( $polkurierIntegrationController = new PolkurierIntegrationController(
$template, $template,
$translator, $translator,
@@ -314,7 +324,6 @@ return static function (Application $app): void {
$deliveryStatusMappingRepository, $deliveryStatusMappingRepository,
$deliveryStatusRepository $deliveryStatusRepository
); );
$companySettingsRepository = new CompanySettingsRepository($app->db());
$companySettingsController = new CompanySettingsController( $companySettingsController = new CompanySettingsController(
$template, $template,
$translator, $translator,
@@ -482,7 +491,6 @@ return static function (Application $app): void {
new MfWhitelistApiClient() new MfWhitelistApiClient()
); );
$allegroApiClient = new AllegroApiClient(); $allegroApiClient = new AllegroApiClient();
$shipmentPackageRepository = new ShipmentPackageRepository($app->db());
$shipmentService = new AllegroShipmentService( $shipmentService = new AllegroShipmentService(
$allegroTokenManager, $allegroTokenManager,
$allegroApiClient, $allegroApiClient,
@@ -503,13 +511,6 @@ return static function (Application $app): void {
$companySettingsRepository, $companySettingsRepository,
new OrdersRepository($app->db()) new OrdersRepository($app->db())
); );
$polkurierShipmentService = new PolkurierShipmentService(
$polkurierIntegrationRepository,
new PolkurierApiClient(),
$shipmentPackageRepository,
$companySettingsRepository,
new OrdersRepository($app->db())
);
$shipmentProviderRegistry = new ShipmentProviderRegistry([ $shipmentProviderRegistry = new ShipmentProviderRegistry([
$shipmentService, $shipmentService,
$apaczkaShipmentService, $apaczkaShipmentService,

View File

@@ -15,6 +15,7 @@ use DateInterval;
use DateTimeImmutable; use DateTimeImmutable;
use App\Core\Constants\IntegrationSources; use App\Core\Constants\IntegrationSources;
use App\Core\Constants\RedirectPaths; use App\Core\Constants\RedirectPaths;
use App\Modules\Shipments\PolkurierShipmentService;
use RuntimeException; use RuntimeException;
use Throwable; use Throwable;
@@ -47,7 +48,8 @@ final class ShopproIntegrationsController
private readonly AllegroOAuthClient $allegroOAuthClient, private readonly AllegroOAuthClient $allegroOAuthClient,
private readonly AllegroApiClient $allegroApiClient, private readonly AllegroApiClient $allegroApiClient,
private readonly ?ApaczkaIntegrationRepository $apaczkaRepository = null, private readonly ?ApaczkaIntegrationRepository $apaczkaRepository = null,
private readonly ?ApaczkaApiClient $apaczkaApiClient = null private readonly ?ApaczkaApiClient $apaczkaApiClient = null,
private readonly ?PolkurierShipmentService $polkurierShipmentService = null
) { ) {
} }
@@ -80,7 +82,7 @@ final class ShopproIntegrationsController
: []; : [];
$deliveryServicesData = $activeTab === 'delivery' $deliveryServicesData = $activeTab === 'delivery'
? $this->loadDeliveryServices() ? $this->loadDeliveryServices()
: [[], [], '']; : [[], [], [], ''];
$deliveryMappings = $selectedIntegration !== null $deliveryMappings = $selectedIntegration !== null
? $this->deliveryMappings->listMappings(IntegrationSources::SHOPPRO, (int) ($selectedIntegration['id'] ?? 0)) ? $this->deliveryMappings->listMappings(IntegrationSources::SHOPPRO, (int) ($selectedIntegration['id'] ?? 0))
: []; : [];
@@ -109,7 +111,8 @@ final class ShopproIntegrationsController
'orderDeliveryMethods' => $orderDeliveryMethods, 'orderDeliveryMethods' => $orderDeliveryMethods,
'allegroDeliveryServices' => $deliveryServicesData[0], 'allegroDeliveryServices' => $deliveryServicesData[0],
'apaczkaDeliveryServices' => $deliveryServicesData[1], 'apaczkaDeliveryServices' => $deliveryServicesData[1],
'allegroDeliveryServicesError' => $deliveryServicesData[2], 'polkurierDeliveryServices' => $deliveryServicesData[2],
'allegroDeliveryServicesError' => $deliveryServicesData[3],
'inpostDeliveryServices' => array_values(array_filter( 'inpostDeliveryServices' => array_values(array_filter(
$deliveryServicesData[0], $deliveryServicesData[0],
static fn (array $svc): bool => stripos((string) ($svc['carrierId'] ?? ''), 'inpost') !== false static fn (array $svc): bool => stripos((string) ($svc['carrierId'] ?? ''), 'inpost') !== false
@@ -383,6 +386,7 @@ final class ShopproIntegrationsController
$carriers = (array) $request->input('carrier', []); $carriers = (array) $request->input('carrier', []);
$allegroMethodIds = (array) $request->input('allegro_delivery_method_id', []); $allegroMethodIds = (array) $request->input('allegro_delivery_method_id', []);
$apaczkaMethodIds = (array) $request->input('apaczka_delivery_method_id', []); $apaczkaMethodIds = (array) $request->input('apaczka_delivery_method_id', []);
$polkurierMethodIds = (array) $request->input('polkurier_delivery_method_id', []);
$credentialsIds = (array) $request->input('allegro_credentials_id', []); $credentialsIds = (array) $request->input('allegro_credentials_id', []);
$carrierIds = (array) $request->input('allegro_carrier_id', []); $carrierIds = (array) $request->input('allegro_carrier_id', []);
$serviceNames = (array) $request->input('allegro_service_name', []); $serviceNames = (array) $request->input('allegro_service_name', []);
@@ -392,10 +396,12 @@ final class ShopproIntegrationsController
foreach ($orderMethods as $index => $orderMethod) { foreach ($orderMethods as $index => $orderMethod) {
$orderMethodValue = trim((string) $orderMethod); $orderMethodValue = trim((string) $orderMethod);
$carrier = trim((string) ($carriers[$index] ?? 'allegro')); $carrier = trim((string) ($carriers[$index] ?? 'allegro'));
$provider = $carrier === 'apaczka' ? 'apaczka' : 'allegro_wza'; $provider = $this->resolveDeliveryProvider($carrier);
$providerServiceId = $provider === 'apaczka' $providerServiceId = match ($provider) {
? trim((string) ($apaczkaMethodIds[$index] ?? '')) 'apaczka' => trim((string) ($apaczkaMethodIds[$index] ?? '')),
: trim((string) ($allegroMethodIds[$index] ?? '')); 'polkurier' => trim((string) ($polkurierMethodIds[$index] ?? '')),
default => trim((string) ($allegroMethodIds[$index] ?? '')),
};
if ($orderMethodValue === '' || $providerServiceId === '') { if ($orderMethodValue === '' || $providerServiceId === '') {
continue; continue;
} }
@@ -423,6 +429,15 @@ final class ShopproIntegrationsController
return Response::redirect($redirectTo); return Response::redirect($redirectTo);
} }
private function resolveDeliveryProvider(string $carrier): string
{
return match (trim($carrier)) {
'apaczka' => 'apaczka',
'polkurier' => 'polkurier',
default => 'allegro_wza',
};
}
/** /**
* @param array<string, mixed>|null $integration * @param array<string, mixed>|null $integration
* @return array<string, mixed> * @return array<string, mixed>
@@ -920,18 +935,35 @@ final class ShopproIntegrationsController
} }
/** /**
* @return array{0: array<int, array<string, mixed>>, 1: array<int, array<string, mixed>>, 2: string} * @return array{0: array<int, array<string, mixed>>, 1: array<int, array<string, mixed>>, 2: array<int, array<string, mixed>>, 3: string}
*/ */
private function loadDeliveryServices(): array private function loadDeliveryServices(): array
{ {
[$allegroServices, $errorMessage] = $this->loadAllegroDeliveryServices(); [$allegroServices, $errorMessage] = $this->loadAllegroDeliveryServices();
[$apaczkaServices, $apaczkaError] = $this->loadApaczkaServices(); [$apaczkaServices, $apaczkaError] = $this->loadApaczkaServices();
$polkurierServices = $this->loadPolkurierServices();
if ($errorMessage === '' && $apaczkaError !== '') { if ($errorMessage === '' && $apaczkaError !== '') {
$errorMessage = $apaczkaError; $errorMessage = $apaczkaError;
} }
return [$allegroServices, $apaczkaServices, $errorMessage]; return [$allegroServices, $apaczkaServices, $polkurierServices, $errorMessage];
}
/**
* @return array<int, array<string, mixed>>
*/
private function loadPolkurierServices(): array
{
if ($this->polkurierShipmentService === null) {
return [];
}
try {
return $this->polkurierShipmentService->getDeliveryServices();
} catch (Throwable) {
return [];
}
} }
/** /**