fix(145): correct polkurier cod return codes

Phase 145 complete:

- send COD.codtype=S for standard Polkurier COD return time

- send COD.return_cod=BA for bank-account transfer

- add regression coverage and PAUL documentation
This commit is contained in:
2026-05-18 15:21:39 +02:00
parent ea039c6e8c
commit d30a459b1e
10 changed files with 492 additions and 30 deletions

View File

@@ -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*

View File

@@ -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*

View File

@@ -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.

View File

@@ -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`

View File

@@ -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
---
<objective>
## 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.
</objective>
<context>
<clarifications>
- **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.
</clarifications>
## 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
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| `sonar-scanner` | required | Po APPLY, przed UNIFY | ○ |
</skills>
<acceptance_criteria>
## 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
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Wydzielic i poprawic budowanie payloadu COD Polkurier</name>
<files>src/Modules/Shipments/PolkurierShipmentService.php</files>
<action>
- 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.
</action>
<verify>C:\xampp\php\php.exe -l src/Modules/Shipments/PolkurierShipmentService.php</verify>
<done>AC-1, AC-2 i AC-3 spelnione w kodzie budujacym payload.</done>
</task>
<task type="auto">
<name>Task 2: Dodac test regresyjny COD dla Polkuriera</name>
<files>tests/Unit/PolkurierShipmentServiceTest.php</files>
<action>
- 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.
</action>
<verify>C:\xampp\php\php.exe -l tests/Unit/PolkurierShipmentServiceTest.php; vendor/bin/phpunit tests/Unit/PolkurierShipmentServiceTest.php jezeli vendor/bin/phpunit jest dostepny</verify>
<done>AC-1, AC-2 i AC-3 zabezpieczone testami albo gap PHPUnit udokumentowany, jesli vendor nadal jest niedostepny.</done>
</task>
<task type="auto">
<name>Task 3: Zaktualizowac dokumentacje techniczna</name>
<files>DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
- 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.
</action>
<verify>rg -n "codtype|return_cod|Polkurier|polkurier" DOCS/ARCHITECTURE.md DOCS/TECH_CHANGELOG.md</verify>
<done>AC-4 spelnione i dokumentacja opisuje nowy kontrakt payloadu.</done>
</task>
</tasks>
<boundaries>
## 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.
</boundaries>
<verification>
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
</verification>
<success_criteria>
- 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.
</success_criteria>
<output>
After completion, create `.paul/phases/145-polkurier-cod-return-time-hotfix/145-01-SUMMARY.md`
</output>

View File

@@ -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*

View File

@@ -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=<servicecode>`, and `provider_service_name=<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.

View File

@@ -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:**

View File

@@ -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<string, mixed> $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();

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Core\Exceptions\ShipmentException;
use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\CompanySettingsRepository;
use App\Modules\Settings\PolkurierApiClient;
use App\Modules\Settings\PolkurierIntegrationRepository;
use App\Modules\Shipments\PolkurierShipmentService;
use App\Modules\Shipments\ShipmentPackageRepository;
use PHPUnit\Framework\TestCase;
use ReflectionMethod;
final class PolkurierShipmentServiceTest extends TestCase
{
private CompanySettingsRepository $companySettings;
private PolkurierShipmentService $service;
protected function setUp(): void
{
$this->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<string, mixed> $formData
* @return array<string, mixed>|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']);
}
}