diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md
index efae255..a004c7f 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.9.0-dev |
-| Status | v3.13 Imported Notes Badge Count Hotfix complete - Phase 144 closed |
-| Last Updated | 2026-05-18 (Phase 144 unified) |
+| Status | v3.14 Polkurier COD Return Time Hotfix complete - Phase 145 closed |
+| Last Updated | 2026-05-18 (Phase 145 unified) |
## Requirements
@@ -144,6 +144,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
- [x] Polkurier Shipment Prepare Prefill: `/orders/{id}/shipment/prepare` rozpoznaje mapowania shopPRO z `provider='polkurier'`, preselectuje przewoznika i usluge oraz nie fallbackuje do Allegro — Phase 142
- [x] Orders List Sidebar UI Hotfix: `/orders/list` startuje bez opisowego boksu "Zamowienia", a zapisany zwiniety sidebar jest stosowany przed pierwszym renderem strony — Phase 143
- [x] Imported Notes Badge Count Hotfix: badge `[N]` na `/orders/list` zlicza wszystkie `order_notes` zamowienia, lacznie z notatkami importowanymi z shopPRO i notatkami autorskimi operatora — Phase 144
+- [x] Polkurier COD Return Time Hotfix: payload pobrania wysyla `COD.codtype='S'` oraz `COD.return_cod='BA'`, zamiast blednego `transfer` w polu terminu zwrotu pobrania — Phase 145
- [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] 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`; od Phase 144 badge korzysta z `notes_count` i liczy wszystkie rekordy `order_notes`, takze importowane ze zrodla. Brak admin override (brak systemu rol w aplikacji) — edit/delete tylko dla autora — Phase 129/144
@@ -293,6 +294,7 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
| Autoryzacja CRUD przez `WHERE user_id = :user_id` + rowCount=0 ⇒ `RuntimeException(403)` (Phase 129) | Eliminacja konieczności osobnego SELECT pre-check'a — atomowy UPDATE/DELETE z filtrem user_id robi to w jednym query. Wzorzec do reuse dla innych zasobow "ownership-based" w aplikacji. | 2026-05-14 | Active |
| Brak admin override dla notatek (Phase 129) — tylko autor edit/delete | Aplikacja nie ma systemu rol (`grep is_admin\|role=` zwrocil 0 trafien). Odlozone do osobnej fazy gdy beda role; obecnie operator ktory dodal notatke moze ja modyfikowac, inni widzą ale nie modyfikują. | 2026-05-14 | Deferred |
| Badge `[N]` w `order_ref` przy nr zamowienia (Phase 129) — neutralny indigo, NIE alertowy | Subtelniejszy niz `.risk-return-badge` (czerwony, alertowy) — notatki to informacja, nie ostrzezenie. Klik scrolluje do `#notes` w szczegolach zamowienia. Pattern do reuse dla kolejnych metryk per-order (np. liczba SMS, liczba dokumentow). | 2026-05-14 | Active |
+| Polkurier COD defaults: `codtype='S'`, `return_cod='BA'` | API Polkurier rozdziela termin zwrotu pobrania (`S/1D/4D/16D`) od sposobu zwrotu (`BA/PO/MB`). orderPRO uzywa standardowego terminu i przelewu na konto, zgodnie z dotychczasowa walidacja rachunku bankowego. | 2026-05-18 | Active |
| Provider-addition recipe dla `/settings/delivery-statuses?tab=mapping` (Phase 130) | 5 punktow edycji w 4 plikach: (1) const definition `XXX_MAP`/`XXX_DESCRIPTIONS` w `DeliveryStatus.php`, (2) rejestracja w `PROVIDER_MAPS`/`PROVIDER_DESCRIPTIONS`, (3) match arms w `normalize()`/`description()`, (4) `PROVIDERS` const w `DeliveryStatusesController` + `DeliveryStatusMappingController`, (5) lista providerow w `DeliveryStatusMappingRepository::countAllUnmappedForBadge()`. Widok `_delivery-status-mappings-content.php` automatycznie iteruje. Pattern do reuse dla kazdego nowego przewoznika. | 2026-05-14 | Active |
| Defaultowe mapowania statusow dostawy hardcoded w kodzie (nie tylko z DB seed) | Spojnosc z InPost/Apaczka/Allegro — wszyscy maja hardcoded fallback w `DeliveryStatus.php`. UI dziala od razu po deploy, niezaleznie czy operator uruchomil migracje seed. DB override (`delivery_status_mappings`) nadal dziala dla kazdego raw statusu — pattern dual-source (kod default + DB override) zachowany. | 2026-05-14 | Active |
@@ -326,6 +328,6 @@ Quick Reference:
---
*PROJECT.md — Updated when requirements or context change*
-*Last updated: 2026-05-18 after Phase 144 closure*
+*Last updated: 2026-05-18 after Phase 145 closure*
diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md
index 6b7b7c9..77312d9 100644
--- a/.paul/ROADMAP.md
+++ b/.paul/ROADMAP.md
@@ -6,6 +6,23 @@ orderPRO to narzedzie do wielokanalowego zarzadzania sprzedaza. Projekt przechod
## Current Milestone
+v3.14 Polkurier COD Return Time Hotfix - Complete
+
+Pilny hotfix tworzenia przesylek pobraniowych Polkurier: API odrzuca `create_order`, bo payload wysyla bledna wartosc czasu zwrotu pobrania. `codtype` ma uzywac kodu terminu (`S`, `1D`, `4D`, `16D`), a `return_cod` kodu sposobu zwrotu (`BA`, `PO`, `MB`).
+
+Progress: 1 of 1 phases complete (100%).
+
+| Phase | Name | Plans | Status |
+|-------|------|-------|--------|
+| 145 | Polkurier COD Return Time Hotfix | 1/1 | Complete (2026-05-18; PHPUnit/Sonar/live smoke follow-up pending) |
+
+### Phase 145: Polkurier COD Return Time Hotfix
+
+Focus: Naprawic payload COD w `PolkurierShipmentService`, aby przesylki pobraniowe wysylaly `codtype='S'` jako standardowy termin zwrotu pobrania oraz `return_cod='BA'` jako przelew na konto bankowe. Brak zmian DB i brak UI konfiguracji terminow w tym hotfixie.
+Plans: 145-01 (complete; `.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-SUMMARY.md`)
+
+## Previous Milestone
+
v3.13 Imported Notes Badge Count Hotfix - Complete
Pilny hotfix dla listy zamowien: notatki zaimportowane ze zrodla, np. shopPRO, maja byc zliczane razem z notatkami autorskimi w badge `[N]` przy numerze zamowienia.
@@ -21,7 +38,7 @@ Progress: 1 of 1 phases complete (100%).
Focus: Zmienic licznik badge notatek na `/orders/list`, aby uzywal wszystkich rekordow `order_notes` dla zamowienia, a nie tylko `note_type='user'`. Zamowienie `1034` z importowana notatka shopPRO powinno pokazac cyfre na liscie.
Plans: 144-01 (complete; `.paul/phases/144-imported-notes-badge-count/144-01-SUMMARY.md`)
-## Previous Milestone
+## Earlier Milestone
v3.12 Orders List Sidebar UI Hotfix - Complete
@@ -687,4 +704,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
---
*Roadmap created: 2026-03-12*
-*Last updated: 2026-05-18 - Phase 144 complete; v3.13 Imported Notes Badge Count Hotfix complete*
+*Last updated: 2026-05-18 - Phase 145 complete; v3.14 Polkurier COD Return Time Hotfix complete*
diff --git a/.paul/STATE.md b/.paul/STATE.md
index 7cd8fcb..b7528d9 100644
--- a/.paul/STATE.md
+++ b/.paul/STATE.md
@@ -5,19 +5,19 @@
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.
-**Current focus:** v3.13 Imported Notes Badge Count Hotfix complete; Phase 144 unified.
+**Current focus:** v3.14 Polkurier COD Return Time Hotfix complete; Phase 145 unified.
## Current Position
-Milestone: v3.13 Imported Notes Badge Count Hotfix
-Phase: 144 of 144 (Imported Notes Badge Count Hotfix) - Complete
-Plan: 144-01 complete
+Milestone: v3.14 Polkurier COD Return Time Hotfix
+Phase: 145 of 145 (Polkurier COD Return Time Hotfix) - Complete
+Plan: 145-01 complete
Status: Milestone complete, ready for next milestone or release decision
-Last activity: 2026-05-18 12:39 - Unified .paul/phases/144-imported-notes-badge-count/144-01-PLAN.md
+Last activity: 2026-05-18 13:28 - Unified .paul/phases/145-polkurier-cod-return-time-hotfix/145-01-PLAN.md
Progress:
-- Milestone v3.13: [##########] 100% (1 of 1 phases complete)
-- Phase 144: [##########] 100% (complete)
+- Milestone v3.14: [##########] 100% (1 of 1 phases complete)
+- Phase 145: [##########] 100% (complete)
## Loop Position
@@ -29,19 +29,19 @@ PLAN -> APPLY -> UNIFY
## Session Continuity
-Last session: 2026-05-18 12:39
-Stopped at: Phase 144 complete; v3.13 milestone complete
+Last session: 2026-05-18 13:28
+Stopped at: Phase 145 complete; v3.14 milestone complete
Next action: Run $paul-complete-milestone or start next milestone planning
-Resume file: .paul/phases/144-imported-notes-badge-count/144-01-SUMMARY.md
+Resume file: .paul/phases/145-polkurier-cod-return-time-hotfix/145-01-SUMMARY.md
## Pending parallel work
- None — Phase 118, 121, 122 wszystkie zacommitowane (8f14851, 360eef1).
## Git State
-Last commit: HEAD fix(144): count imported order notes in list badge
-Last phase commit: HEAD fix(144): count imported order notes in list badge
-Previous: fix(143): polish orders list and sidebar first paint
+Last commit: HEAD fix(145): correct polkurier cod return codes
+Last phase commit: HEAD fix(145): correct polkurier cod return codes
+Previous: fix(144): count imported order notes in list badge
Branch: main
### Skill Audit (Phase 139)
@@ -81,6 +81,12 @@ Branch: main
|----------|---------|-------|
| `sonar-scanner` | gap documented | Attempted after APPLY with `sonar-scanner --version`; CLI is not available in PATH. |
+### Skill Audit (Phase 145)
+
+| Expected | Invoked | Notes |
+|----------|---------|-------|
+| `sonar-scanner` | gap documented | Attempted after APPLY with `sonar-scanner --version`; CLI is not available in PATH. |
+
### Skill Audit (Phase 129)
| Expected | Invoked | Notes |
@@ -139,6 +145,7 @@ Branch: main
### Recent Decisions
+- Phase 145 fixed the Polkurier COD payload contract: `COD.codtype='S'` for standard return time and `COD.return_cod='BA'` for bank-account transfer. No UI configurability or schema change was added.
- Phase 144 changed the `/orders/list` notes badge contract from operator-only notes to all `order_notes` rows; detail view grouping and note CRUD/import behavior remain unchanged.
- Phase 134 is documentation-only: no runtime code or schema changes were made.
- Backlog entries are annotated, not deleted; stale/implemented cleanup is deferred to later phases.
@@ -159,6 +166,7 @@ Branch: main
### Blockers / Concerns
+- Phase 145 APPLY: `vendor/bin/phpunit` is missing, so `tests/Unit/PolkurierShipmentServiceTest.php` was linted and covered by an ad-hoc runtime smoke instead of PHPUnit; `sonar-scanner` is unavailable in PATH.
- Phase 144 APPLY: `vendor/bin/phpunit` is missing, so `tests/Unit/OrdersRepositoryNotesCountTest.php` was linted and covered by an ad-hoc SQLite runtime smoke instead of PHPUnit; `sonar-scanner` is unavailable in PATH.
- Phase 134: `sonar-scanner` is still unavailable in PATH.
- Phase 135: `vendor/bin/phpunit` and `sonar-scanner` are unavailable in PATH/checkout; syntax checks and ad-hoc SQLite/runtime smoke passed.
@@ -177,6 +185,9 @@ Branch: main
## Pending Actions
+- Phase 145 follow-up: after restoring `vendor/`, run `vendor/bin/phpunit tests/Unit/PolkurierShipmentServiceTest.php`.
+- Phase 145 follow-up: run SonarQube scan after restoring `sonar-scanner` in PATH.
+- Phase 145 follow-up: with explicit operator intent, create one live Polkurier COD shipment and confirm API accepts `codtype='S'` / `return_cod='BA'` without the previous return-time error.
- 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) 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.
diff --git a/.paul/changelog/2026-05-18.md b/.paul/changelog/2026-05-18.md
index ad5a6c2..142badb 100644
--- a/.paul/changelog/2026-05-18.md
+++ b/.paul/changelog/2026-05-18.md
@@ -24,6 +24,10 @@
- `OrdersRepository` zwraca `notes_count` liczony ze wszystkich rekordow `order_notes` dla zamowienia.
- Dodano test regresyjny `OrdersRepositoryNotesCountTest` i wykonano SQLite smoke `notes_count=2` dla notatki `message` + `user`.
- Gap: PHPUnit i SonarQube scan Phase 144 pozostaja do wykonania po przywroceniu `vendor/` i `sonar-scanner`.
+- [Phase 145, Plan 01] Naprawiono payload COD Polkuriera, ktory powodowal blad `create_order` o dozwolonym zbiorze `[S, 1D, 4D, 16D]`.
+- `PolkurierShipmentService` wysyla teraz `COD.codtype='S'` oraz `COD.return_cod='BA'`, zachowujac walidacje i czyszczenie numeru konta bankowego.
+- Dodano test regresyjny `PolkurierShipmentServiceTest` oraz ad-hoc runtime smoke dla payloadu COD.
+- Gap: PHPUnit i SonarQube scan Phase 145 pozostaja do wykonania po przywroceniu `vendor/` i `sonar-scanner`.
## Zmienione pliki
@@ -61,3 +65,7 @@
- `src/Modules/Orders/OrdersController.php`
- `tests/Unit/OrdersRepositoryNotesCountTest.php`
- `DOCS/DB_SCHEMA.md`
+- `.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-PLAN.md`
+- `.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-SUMMARY.md`
+- `src/Modules/Shipments/PolkurierShipmentService.php`
+- `tests/Unit/PolkurierShipmentServiceTest.php`
diff --git a/.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-PLAN.md b/.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-PLAN.md
new file mode 100644
index 0000000..36a1387
--- /dev/null
+++ b/.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-PLAN.md
@@ -0,0 +1,180 @@
+---
+phase: 145-polkurier-cod-return-time-hotfix
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - src/Modules/Shipments/PolkurierShipmentService.php
+ - tests/Unit/PolkurierShipmentServiceTest.php
+ - DOCS/ARCHITECTURE.md
+ - DOCS/TECH_CHANGELOG.md
+autonomous: true
+delegation: off
+---
+
+
+## Goal
+Naprawic tworzenie przesylek pobraniowych Polkurier, aby payload COD wysylal do API dozwolony termin zwrotu pobrania.
+
+## Purpose
+Operator nie moze utworzyc przesylki pobraniowej, bo Polkurier odrzuca `create_order` komunikatem: "Parametr czasu zwrotu pobrania musi zawierac sie w zbiorze [S, 1D, 4D, 16D]". Obecny payload myli sposob zwrotu pobrania z terminem zwrotu pobrania.
+
+## Output
+Poprawiony payload COD w `PolkurierShipmentService`, test regresyjny budowania danych COD oraz aktualizacja dokumentacji technicznej.
+
+
+
+
+- **Brak pytan** - Problem jest jednoznaczny po analizie kodu i dokumentacji Polkurier: `codtype` oznacza termin zwrotu pobrania (`S`, `1D`, `4D`, `16D`), a `return_cod` oznacza sposob zwrotu (`BA`, `PO`, `MB`). Obecny kod wysyla `return_cod='transfer'`, a `codtype='transfer'`.
+ -> Odpowiedz: No clarifications needed - proceeding.
+
+
+## Project Context
+@AGENTS.md
+@.paul/PROJECT.md
+@.paul/ROADMAP.md
+@.paul/STATE.md
+@DOCS/ARCHITECTURE.md
+@DOCS/DB_SCHEMA.md
+
+## Prior Work
+@.paul/phases/127-polkurier-integration-foundation/127-01-PLAN.md
+@.paul/phases/128-polkurier-shipment-service/128-01-PLAN.md
+@.paul/phases/140-shoppro-polkurier-delivery-mapping/140-01-PLAN.md
+@.paul/phases/142-polkurier-shipment-prepare-prefill/142-01-PLAN.md
+
+## Source Files
+@src/Modules/Shipments/PolkurierShipmentService.php
+@src/Modules/Settings/PolkurierApiClient.php
+@src/Modules/Shipments/ShipmentPackageRepository.php
+@tests/Unit/ApaczkaShipmentServiceTest.php
+@tests/Unit/ShipmentPreparePolkurierMappingTest.php
+
+
+
+## Required Skills (from SPECIAL-FLOWS.md)
+
+| Skill | Priority | When to Invoke | Loaded? |
+|-------|----------|----------------|---------|
+| `sonar-scanner` | required | Po APPLY, przed UNIFY | ○ |
+
+
+
+
+
+## AC-1: Poprawny Termin Zwrotu Pobrania
+```gherkin
+Given operator tworzy przesylke Polkurier z kwota pobrania wieksza od 0
+When orderPRO buduje payload `create_order`
+Then sekcja `COD.codtype` zawiera dozwolona wartosc terminu zwrotu pobrania, domyslnie `S`
+And nie zawiera juz blednej wartosci `transfer`
+```
+
+## AC-2: Poprawny Sposob Zwrotu Pobrania
+```gherkin
+Given operator tworzy przesylke Polkurier z pobraniem na konto bankowe firmy
+When orderPRO buduje payload `create_order`
+Then sekcja `COD.return_cod` zawiera dozwolony kod sposobu zwrotu, domyslnie `BA`
+And `COD.codbankaccount` nadal zawiera oczyszczony numer konta bankowego
+```
+
+## AC-3: Brak Regresji Dla Przesylek Bez COD
+```gherkin
+Given operator tworzy zwykla przesylke Polkurier bez pobrania
+When orderPRO buduje payload `create_order`
+Then sekcja `COD` nie jest dodawana
+And dotychczasowy zapis paczki i payload pozostaja bez zmian
+```
+
+## AC-4: Brak Zmiany Schematu
+```gherkin
+Given hotfix dotyczy tylko mapowania wartosci wysylanych do API Polkurier
+When plan zostanie wdrozony
+Then nie powstaje migracja ani zmiana struktury tabel
+```
+
+
+
+
+
+
+ Task 1: Wydzielic i poprawic budowanie payloadu COD Polkurier
+ src/Modules/Shipments/PolkurierShipmentService.php
+
+ - Zastap obecny inline blok `COD` mala prywatna metoda odpowiedzialna tylko za COD.
+ - Metoda ma zwracac `null`, gdy `cod_amount <= 0`, zeby przesylki bez pobrania nie dostawaly sekcji `COD`.
+ - Dla pobrania ustaw `codtype` na dozwolony termin zwrotu pobrania. Domyslnie uzyj `S`, bo dokumentacja opisuje to jako standardowy zwrot 5-7 dni roboczych.
+ - Dla sposobu zwrotu ustaw `return_cod` na `BA`, czyli przelew na konto bankowe.
+ - Zachowaj obecna walidacje numeru konta: brak rachunku bankowego nadal ma rzucac `ShipmentException` z aktualnym komunikatem.
+ - Nie dodawaj UI ani nowych ustawien; to hotfix blednego payloadu, nie konfigurator terminow COD.
+
+ C:\xampp\php\php.exe -l src/Modules/Shipments/PolkurierShipmentService.php
+ AC-1, AC-2 i AC-3 spelnione w kodzie budujacym payload.
+
+
+
+ Task 2: Dodac test regresyjny COD dla Polkuriera
+ tests/Unit/PolkurierShipmentServiceTest.php
+
+ - Dodaj test jednostkowy dla prywatnej metody budujacej COD przez `ReflectionMethod`, zgodnie ze stylem `ApaczkaShipmentServiceTest`.
+ - Assert dla pobrania: `codtype === 'S'`, `return_cod === 'BA'`, `codamount` jest liczba zaokraglona do 2 miejsc, a `codbankaccount` ma same cyfry.
+ - Dodaj test, ze `cod_amount=0` zwraca `null`.
+ - Dodaj test, ze pobranie bez numeru konta rzuca `ShipmentException`.
+ - Test ma uzywac mockow repozytoriow/API i nie ma laczyc sie z Polkurier ani baza danych.
+
+ C:\xampp\php\php.exe -l tests/Unit/PolkurierShipmentServiceTest.php; vendor/bin/phpunit tests/Unit/PolkurierShipmentServiceTest.php jezeli vendor/bin/phpunit jest dostepny
+ AC-1, AC-2 i AC-3 zabezpieczone testami albo gap PHPUnit udokumentowany, jesli vendor nadal jest niedostepny.
+
+
+
+ Task 3: Zaktualizowac dokumentacje techniczna
+ DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md
+
+ - W `DOCS/ARCHITECTURE.md` dopisz kontrakt COD dla Polkuriera: `codtype='S'` jako standardowy termin zwrotu i `return_cod='BA'` jako przelew na konto.
+ - W `DOCS/TECH_CHANGELOG.md` dodaj wpis 2026-05-18 opisujacy hotfix bledu `create_order` przy pobraniu.
+ - Nie aktualizuj `DOCS/DB_SCHEMA.md`, bo nie ma migracji ani zmiany tabel.
+
+ rg -n "codtype|return_cod|Polkurier|polkurier" DOCS/ARCHITECTURE.md DOCS/TECH_CHANGELOG.md
+ AC-4 spelnione i dokumentacja opisuje nowy kontrakt payloadu.
+
+
+
+
+
+
+## DO NOT CHANGE
+- `database/migrations/*` - brak zmiany schematu.
+- `resources/views/shipments/prepare.php` - formularz prefillu byl naprawiany w Phase 142; ten blad jest w payloadzie API.
+- `src/Modules/Settings/PolkurierApiClient.php` - wrapper API i obsluga bledow zostaja bez zmian.
+- `src/Modules/Settings/ShopproIntegrationsController.php` oraz widoki mapowan shopPRO - mapowania dostaw nie sa przyczyna bledu COD.
+
+## SCOPE LIMITS
+- Nie dodawac konfiguracji wyboru `S/1D/4D/16D` w UI w tym planie.
+- Nie dodawac przekazu pocztowego `PO` ani skarbonki `MB`; domyslny i wymagany teraz przypadek to przelew bankowy.
+- Nie wykonywac realnego utworzenia przesylki Polkurier bez jawnej decyzji operatora, bo to moze nadac prawdziwa paczke.
+- Nie zmieniac logiki etykiet, statusow, trackingu ani mapowan shopPRO.
+
+
+
+
+Before declaring plan complete:
+- [ ] `C:\xampp\php\php.exe -l src/Modules/Shipments/PolkurierShipmentService.php`
+- [ ] `C:\xampp\php\php.exe -l tests/Unit/PolkurierShipmentServiceTest.php`
+- [ ] `vendor/bin/phpunit tests/Unit/PolkurierShipmentServiceTest.php` jezeli zaleznosci sa dostepne
+- [ ] `rg -n "codtype|return_cod|Polkurier|polkurier" DOCS/ARCHITECTURE.md DOCS/TECH_CHANGELOG.md`
+- [ ] `git diff --check`
+- [ ] `sonar-scanner` jezeli CLI jest dostepny; w przeciwnym razie udokumentowac gap
+
+
+
+- Przesylka pobraniowa Polkurier nie wysyla juz `transfer` w polu wymagajacym `[S, 1D, 4D, 16D]`.
+- Payload COD uzywa `codtype='S'` i `return_cod='BA'`.
+- Przesylki bez pobrania nadal nie zawieraja sekcji `COD`.
+- Walidacja numeru konta bankowego zostaje zachowana.
+- Dokumentacja techniczna odzwierciedla hotfix.
+
+
+
diff --git a/.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-SUMMARY.md b/.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-SUMMARY.md
new file mode 100644
index 0000000..65bdb48
--- /dev/null
+++ b/.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-SUMMARY.md
@@ -0,0 +1,133 @@
+---
+phase: 145-polkurier-cod-return-time-hotfix
+plan: 01
+subsystem: shipments
+tags: [polkurier, cod, payload, shipments]
+requires:
+ - phase: 128-polkurier-shipment-service
+ provides: Polkurier shipment creation flow and COD payload support
+provides:
+ - Polkurier COD payload uses API-valid return-time and return-method codes
+ - Regression test for COD payload construction
+affects: [polkurier, shipments, cod]
+tech-stack:
+ added: []
+ patterns: [private payload builder covered by reflection-based unit test]
+key-files:
+ created:
+ - tests/Unit/PolkurierShipmentServiceTest.php
+ modified:
+ - src/Modules/Shipments/PolkurierShipmentService.php
+ - DOCS/ARCHITECTURE.md
+ - DOCS/TECH_CHANGELOG.md
+key-decisions:
+ - "Polkurier COD return time defaults to `S` and return method defaults to `BA`."
+patterns-established:
+ - "Provider-specific API code mappings stay in the shipment service unless UI configurability is explicitly required."
+duration: 13min
+started: 2026-05-18T13:05:00+02:00
+completed: 2026-05-18T13:18:00+02:00
+---
+
+# Phase 145 Plan 01: Polkurier COD Return Time Hotfix Summary
+
+Polkurier cash-on-delivery shipments now send API-valid COD codes: `COD.codtype='S'` and `COD.return_cod='BA'`.
+
+## Performance
+
+| Metric | Value |
+|--------|-------|
+| Duration | 13min |
+| Started | 2026-05-18 13:05 |
+| Completed | 2026-05-18 13:18 |
+| Tasks | 3 completed |
+| Files modified | 4 runtime/test/docs files + PAUL docs |
+
+## Acceptance Criteria Results
+
+| Criterion | Status | Notes |
+|-----------|--------|-------|
+| AC-1: Poprawny Termin Zwrotu Pobrania | Pass | `buildCodPayload()` sends `codtype='S'`, not `transfer`. |
+| AC-2: Poprawny Sposob Zwrotu Pobrania | Pass | `return_cod='BA'`; `codbankaccount` is sanitized to digits. |
+| AC-3: Brak Regresji Dla Przesylek Bez COD | Pass | `cod_amount <= 0` returns `null`, so `COD` is omitted. |
+| AC-4: Brak Zmiany Schematu | Pass | No migrations or DB schema changes. |
+
+## Accomplishments
+
+- Replaced invalid Polkurier COD values that caused `create_order` to reject COD shipments.
+- Preserved the existing company bank-account requirement for COD shipments.
+- Added a unit-level regression test for COD payload shape, zero-COD omission, and missing bank-account validation.
+- Updated architecture and technical changelog documentation.
+
+## Task Commits
+
+No task-level commits were created during APPLY. Phase commit is created during UNIFY.
+
+## Files Created/Modified
+
+| File | Change | Purpose |
+|------|--------|---------|
+| `src/Modules/Shipments/PolkurierShipmentService.php` | Modified | Extracted COD payload builder and set Polkurier-valid codes. |
+| `tests/Unit/PolkurierShipmentServiceTest.php` | Created | Regression coverage for COD payload construction. |
+| `DOCS/ARCHITECTURE.md` | Modified | Documents the Polkurier COD payload contract. |
+| `DOCS/TECH_CHANGELOG.md` | Modified | Records the Phase 145 hotfix. |
+| `.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-PLAN.md` | Created | Executable PAUL plan. |
+| `.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-SUMMARY.md` | Created | Completion record. |
+
+## Decisions Made
+
+| Decision | Rationale | Impact |
+|----------|-----------|--------|
+| Default COD return time is `S` | Polkurier accepts `[S, 1D, 4D, 16D]`; `S` is the standard return-time option. | Fixes the reported API error without adding UI scope. |
+| Default COD return method is `BA` | Current orderPRO flow requires a company bank account, so bank transfer is the matching return method. | Keeps behavior aligned with existing validation. |
+| No UI configurability for `1D/4D/16D` | The request is a hotfix for invalid payload values. | Avoids scope creep and schema/config changes. |
+
+## Deviations from Plan
+
+### Summary
+
+| Type | Count | Impact |
+|------|-------|--------|
+| Auto-fixed | 0 | None |
+| Scope additions | 0 | None |
+| Deferred | 2 | Environment-only verification gaps |
+
+### Deferred Items
+
+- Run `vendor/bin/phpunit tests/Unit/PolkurierShipmentServiceTest.php` after dependencies are restored.
+- Run `sonar-scanner` after the CLI is restored in PATH.
+
+## Issues Encountered
+
+| Issue | Resolution |
+|-------|------------|
+| `vendor/bin/phpunit` missing | Linted the test and ran an ad-hoc runtime smoke through `C:\xampp\php\php.exe`. |
+| `sonar-scanner` missing in PATH | Documented the required-skill gap in STATE/SUMMARY. |
+
+## Verification Results
+
+| Check | Result |
+|-------|--------|
+| `C:\xampp\php\php.exe -l src/Modules/Shipments/PolkurierShipmentService.php` | Pass |
+| `C:\xampp\php\php.exe -l tests/Unit/PolkurierShipmentServiceTest.php` | Pass |
+| Ad-hoc runtime smoke for `buildCodPayload()` | Pass |
+| `rg -n "codtype|return_cod|Polkurier|polkurier" DOCS/ARCHITECTURE.md DOCS/TECH_CHANGELOG.md` | Pass |
+| `git diff --check` | Pass; only Windows line-ending warnings |
+| `vendor/bin/phpunit tests/Unit/PolkurierShipmentServiceTest.php` | Gap: binary missing |
+| `sonar-scanner --version` | Gap: CLI missing in PATH |
+
+## Next Phase Readiness
+
+**Ready:**
+- Polkurier COD shipments should no longer fail on invalid return-time value.
+- Test file is ready to run once PHPUnit dependencies return.
+
+**Concerns:**
+- Live Polkurier COD shipment smoke was not executed to avoid creating a real shipment without explicit operator action.
+
+**Blockers:**
+- None for code completion.
+
+---
+*Phase: 145-polkurier-cod-return-time-hotfix, Plan: 01*
+*Completed: 2026-05-18*
diff --git a/DOCS/ARCHITECTURE.md b/DOCS/ARCHITECTURE.md
index 1d382fb..b9c4d2c 100644
--- a/DOCS/ARCHITECTURE.md
+++ b/DOCS/ARCHITECTURE.md
@@ -156,6 +156,7 @@ Phase 135 fixes daily net totals: `OrdersStatisticsRepository::netAmountSql()` p
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=`, and `provider_service_name=`. No schema change is required; the shipment prepare flow reads the shared mapping and preselects `provider='polkurier'` with the stored delivery method id instead of falling back to Allegro.
+Phase 145 fixes the Polkurier COD payload contract. For cash-on-delivery shipments, `PolkurierShipmentService` sends `COD.codtype='S'` (standard return time) and `COD.return_cod='BA'` (bank-account transfer), while preserving the existing bank-account requirement and sanitized numeric `codbankaccount`.
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.
diff --git a/DOCS/TECH_CHANGELOG.md b/DOCS/TECH_CHANGELOG.md
index 18476c3..5cff406 100644
--- a/DOCS/TECH_CHANGELOG.md
+++ b/DOCS/TECH_CHANGELOG.md
@@ -1,5 +1,19 @@
# Technical Changelog
+## 2026-05-18 - Phase 145 Plan 01: Polkurier COD Return Time Hotfix
+
+**Co zrobiono:**
+- Naprawiono payload COD wysylany do Polkurier `create_order`.
+- `PolkurierShipmentService` wysyla teraz `COD.codtype='S'` jako standardowy termin zwrotu pobrania oraz `COD.return_cod='BA'` jako przelew na konto bankowe.
+- Zachowano oczyszczanie numeru rachunku bankowego do samych cyfr i dotychczasowy blad walidacji, gdy konto firmowe nie jest uzupelnione.
+- Dodano test regresyjny budowania payloadu COD dla Polkuriera.
+
+**Dlaczego:**
+- API Polkurier odrzucalo przesylki pobraniowe komunikatem o dozwolonym zbiorze `[S, 1D, 4D, 16D]`, bo orderPRO wysylal wartosc `transfer` w polu terminu zwrotu pobrania.
+
+**BREAKING / migracja:**
+- Brak migracji DB i brak zmian breaking. Hotfix zmienia tylko wartosci wysylane w sekcji `COD` payloadu Polkurier.
+
## 2026-05-18 - Phase 144 Plan 01: Imported Notes Badge Count Hotfix
**Co zrobiono:**
diff --git a/src/Modules/Shipments/PolkurierShipmentService.php b/src/Modules/Shipments/PolkurierShipmentService.php
index 816c2f4..a3a5ee6 100644
--- a/src/Modules/Shipments/PolkurierShipmentService.php
+++ b/src/Modules/Shipments/PolkurierShipmentService.php
@@ -150,18 +150,9 @@ final class PolkurierShipmentService implements ShipmentProviderInterface
}
$cod = max(0.0, (float) ($formData['cod_amount'] ?? 0));
- if ($cod > 0) {
- $companySettings = $this->companySettings->getSettings();
- $bankAccount = preg_replace('/[^0-9]/', '', (string) ($companySettings['bank_account'] ?? '')) ?? '';
- if ($bankAccount === '') {
- throw new ShipmentException('Przesylka COD wymaga numeru konta bankowego. Uzupelnij go w Ustawienia > Firma.');
- }
- $apiPayload['COD'] = [
- 'codtype' => 'transfer',
- 'codamount' => round($cod, 2),
- 'codbankaccount' => $bankAccount,
- 'return_cod' => 'transfer',
- ];
+ $codPayload = $this->buildCodPayload($formData);
+ if ($codPayload !== null) {
+ $apiPayload['COD'] = $codPayload;
}
$carrierLabel = $this->resolveCarrierLabel($courierCode);
@@ -618,6 +609,31 @@ final class PolkurierShipmentService implements ShipmentProviderInterface
];
}
+ /**
+ * @param array $formData
+ * @return array{codtype: string, codamount: float, codbankaccount: string, return_cod: string}|null
+ */
+ private function buildCodPayload(array $formData): ?array
+ {
+ $cod = max(0.0, (float) ($formData['cod_amount'] ?? 0));
+ if ($cod <= 0) {
+ return null;
+ }
+
+ $companySettings = $this->companySettings->getSettings();
+ $bankAccount = preg_replace('/[^0-9]/', '', (string) ($companySettings['bank_account'] ?? '')) ?? '';
+ if ($bankAccount === '') {
+ throw new ShipmentException('Przesylka COD wymaga numeru konta bankowego. Uzupelnij go w Ustawienia > Firma.');
+ }
+
+ return [
+ 'codtype' => 'S',
+ 'codamount' => round($cod, 2),
+ 'codbankaccount' => $bankAccount,
+ 'return_cod' => 'BA',
+ ];
+ }
+
private function nextBusinessDay(): string
{
$ts = time();
diff --git a/tests/Unit/PolkurierShipmentServiceTest.php b/tests/Unit/PolkurierShipmentServiceTest.php
new file mode 100644
index 0000000..5322608
--- /dev/null
+++ b/tests/Unit/PolkurierShipmentServiceTest.php
@@ -0,0 +1,80 @@
+companySettings = $this->createMock(CompanySettingsRepository::class);
+ $this->service = new PolkurierShipmentService(
+ $this->createMock(PolkurierIntegrationRepository::class),
+ $this->createMock(PolkurierApiClient::class),
+ $this->createMock(ShipmentPackageRepository::class),
+ $this->companySettings,
+ $this->createMock(OrdersRepository::class)
+ );
+ }
+
+ /**
+ * @param array $formData
+ * @return array|null
+ */
+ private function invokeBuildCodPayload(array $formData): ?array
+ {
+ $method = new ReflectionMethod(PolkurierShipmentService::class, 'buildCodPayload');
+ $method->setAccessible(true);
+ $result = $method->invoke($this->service, $formData);
+
+ return is_array($result) ? $result : null;
+ }
+
+ public function testBuildCodPayloadUsesPolkurierAllowedCodes(): void
+ {
+ $this->companySettings
+ ->method('getSettings')
+ ->willReturn(['bank_account' => '12 3456-7890 1234 5678 9012 3456']);
+
+ $payload = $this->invokeBuildCodPayload(['cod_amount' => '123.456']);
+
+ self::assertSame('S', $payload['codtype'] ?? null);
+ self::assertSame(123.46, $payload['codamount'] ?? null);
+ self::assertSame('12345678901234567890123456', $payload['codbankaccount'] ?? null);
+ self::assertSame('BA', $payload['return_cod'] ?? null);
+ }
+
+ public function testBuildCodPayloadReturnsNullWithoutCodAmount(): void
+ {
+ $this->companySettings
+ ->expects(self::never())
+ ->method('getSettings');
+
+ self::assertNull($this->invokeBuildCodPayload(['cod_amount' => '0']));
+ }
+
+ public function testBuildCodPayloadRequiresBankAccount(): void
+ {
+ $this->companySettings
+ ->method('getSettings')
+ ->willReturn(['bank_account' => '']);
+
+ $this->expectException(ShipmentException::class);
+ $this->expectExceptionMessage('Przesylka COD wymaga numeru konta bankowego.');
+
+ $this->invokeBuildCodPayload(['cod_amount' => '50.00']);
+ }
+}