feat(128): polkurier shipment service + tracking + UI prepare
PolkurierApiClient rozszerzony do pelnego kontraktu (7 metod):
createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers/
getInpostParcelMachines/getCourierPoints. Wspolny call() parsuje
envelope {status, response}. Kontrakt zweryfikowany na oficjalnej
dokumentacji PDF v1.11.
PolkurierShipmentService (implements ShipmentProviderInterface)
orchestruje pelen flow: normalizeShipmentType (lowercase), split
ulicy, build recipient/sender/pickup, COD z bank account z
company_settings, extractOrderNumber/extractTrackingNumber
priorytetujace SDK Order entity (number, waybills[0].number).
PolkurierTrackingService (implements ShipmentTrackingInterface)
mapuje statusy O/P/A/WP/D/Z/W przez delivery_status_mappings.
UI panel polkurier w prepare.php z dynamiczna lista uslug z
available_carriers. Bez dedykowanego selektora punktu — operator
wpisuje receiver_point_id w istniejace pole w sekcji Adres odbiorcy.
Migracja 20260514_000115 seedujaca 7 wpisow delivery_status_mappings
z oficjalnej tabeli ORDER_STATUS (O/P/A/WP/D/Z/W).
Live test #114/#115 zakonczony sukcesem po 4 iteracjach
(ReferenceError -> uppercase shipmenttype -> orderno parsing ->
A4/A6 etykieta). Rozmiar etykiety A4/A6 sterowany w panelu klienta
polkurier.pl, NIE przez API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
392
.paul/phases/128-polkurier-shipment-service/128-01-PLAN.md
Normal file
392
.paul/phases/128-polkurier-shipment-service/128-01-PLAN.md
Normal file
@@ -0,0 +1,392 @@
|
||||
---
|
||||
phase: 128-polkurier-shipment-service
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/Modules/Settings/PolkurierApiClient.php
|
||||
- src/Modules/Shipments/PolkurierShipmentService.php
|
||||
- src/Modules/Shipments/PolkurierTrackingService.php
|
||||
- src/Modules/Shipments/ShipmentController.php
|
||||
- src/Modules/Shipments/DeliveryStatus.php
|
||||
- src/Modules/Cron/CronHandlerFactory.php
|
||||
- routes/web.php
|
||||
- resources/views/shipments/prepare.php
|
||||
- database/migrations/20260514_000115_seed_polkurier_delivery_status_mappings.sql
|
||||
- .paul/codebase/architecture.md
|
||||
- .paul/codebase/db_schema.md
|
||||
- .paul/codebase/tech_changelog.md
|
||||
autonomous: false
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Dostarczyc pelna integracje wysylkowa polkurier.pl: tworzenie paczek (createOrder), pobieranie etykiet (getLabel), tracking statusow przesylek (getStatus) oraz UI do nadawania paczek w `/orders/{id}/shipment/prepare` z dynamiczna lista przewoznikow z polkuriera i obsluga punktow odbioru (Paczkomaty InPost, ORLEN, Pocztex, Kurier48). Weryfikacja na zywych zamowieniach #114 i #115 z manualnym anulowaniem po teście.
|
||||
|
||||
## Purpose
|
||||
Phase 127 dostarczyl fundament (settings + test_auth_api). Bez ShipmentService polkurier jest "wystawiony w hubie integracji ale niedzialajacy". Operator chce realnie nadawac paczki przez polkurier obok Apaczki — wspolny use case (DPD, UPS, GLS, InPost) z lepszymi cenami.
|
||||
|
||||
## Output
|
||||
- 2 nowe klasy w `src/Modules/Shipments/` (`PolkurierShipmentService`, `PolkurierTrackingService`) implementujace odpowiednio `ShipmentProviderInterface` i `ShipmentTrackingInterface`
|
||||
- Rozszerzony `PolkurierApiClient` z 6 nowymi metodami API
|
||||
- Nowy panel "polkurier" w `prepare.php` + przelacznik JS
|
||||
- Migracja seedujaca `delivery_status_mappings` (provider='polkurier')
|
||||
- Architektura/schema/changelog zaktualizowane
|
||||
- 2 paczki utworzone na zywym koncie polkurier (#114 i #115), zweryfikowane, recznie anulowane przez operatora
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
|
||||
<clarifications>
|
||||
- **Zakres fazy** — Co dostarczamy w Phase 128?
|
||||
→ Odpowiedz: Pelny zakres: ShipmentService + TrackingService + UI prepare + delivery_status_mappings (jedna duza faza zamiast dwoch).
|
||||
- **Uslugi UI** — Jak prezentujemy uslugi polkurier w UI prepare?
|
||||
→ Odpowiedz: Dynamiczna lista z API polkuriera (`get_available_carriers` lub odpowiednia metoda) — analog `ApaczkaApiClient::getServiceStructure`.
|
||||
- **Paczkomaty** — Czy obslugujemy paczkomaty/punkty odbioru w Phase 128?
|
||||
→ Odpowiedz: Pelne wsparcie wszystkich punktow (InpostParcelMachines, PocztexPostOffices, Kurier48PostOffices, ewentualnie ORLEN).
|
||||
- **Tryb testu** — Jak testujemy na #114/#115?
|
||||
→ Odpowiedz: Realny createOrder na zywym koncie + manualny cancelOrder po weryfikacji przez operatora w panelu polkurier (live test, ale bez wysylki).
|
||||
</clarifications>
|
||||
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
@.paul/codebase/architecture.md
|
||||
@.paul/codebase/db_schema.md
|
||||
|
||||
## Prior Work
|
||||
@.paul/phases/127-polkurier-integration-foundation/127-01-SUMMARY.md
|
||||
|
||||
## Source Files (wzorzec Apaczka)
|
||||
@src/Modules/Shipments/ApaczkaShipmentService.php
|
||||
@src/Modules/Shipments/ApaczkaTrackingService.php
|
||||
@src/Modules/Settings/ApaczkaApiClient.php
|
||||
@src/Modules/Shipments/ShipmentProviderInterface.php
|
||||
@src/Modules/Shipments/ShipmentTrackingInterface.php
|
||||
@src/Modules/Shipments/ShipmentProviderRegistry.php
|
||||
@src/Modules/Settings/PolkurierApiClient.php
|
||||
@src/Modules/Settings/PolkurierIntegrationRepository.php
|
||||
@src/Modules/Shipments/ShipmentController.php
|
||||
@src/Modules/Cron/CronHandlerFactory.php
|
||||
@resources/views/shipments/prepare.php
|
||||
@routes/web.php
|
||||
|
||||
## External Reference
|
||||
- Oficjalne polkurier SDK: https://github.com/Polkurier/polkurier-sdk (zweryfikowany kontrakt API w Phase 127)
|
||||
- Klucze apimetod w SDK: `test_auth_api`, `new_order`, `get_label`, `get_status`, `cancel_order`, `get_available_carriers`, `get_parcel_machines`, `get_post_offices`, `get_carrier_info` (do potwierdzenia per SDK)
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: PolkurierApiClient — pelny kontrakt API
|
||||
```gherkin
|
||||
Given globalna konfiguracja polkurier jest aktywna i token zwery (Phase 127)
|
||||
When backend wywoluje `PolkurierApiClient::getAvailableCarriers()` z poprawnymi credentials
|
||||
Then klient zwraca tablice przewoznikow (DPD/UPS/GLS/InPost/Pocztex…) z polami: `carrier_id`, `name`, `service_code`, `supports_pickup_point` (bool), `weight_limits`, `cod_supported`
|
||||
And `createShipment($payload)` zwraca tablice `{order_id: string, tracking_number: string, label_url: ?string, raw: array}` przy `status='success'`
|
||||
And `getLabel($orderId, $format)` zwraca binarna zawartosc etykiety (PDF/ZPL/EPL zaleznie od `default_label_format`)
|
||||
And `getStatus($orderId)` zwraca tablice `{status_code, status_name, status_date}`
|
||||
And `cancelOrder($orderId)` zwraca `{ok: bool, message: string}`
|
||||
And kazda metoda przy `status != 'success'` rzuca `RuntimeException` z trescia z pola `response`
|
||||
```
|
||||
|
||||
## AC-2: PolkurierShipmentService implementuje ShipmentProviderInterface
|
||||
```gherkin
|
||||
Given `PolkurierShipmentService` jest zarejestrowany w `ShipmentProviderRegistry` jako `code()='polkurier'`
|
||||
When `createShipment(int $orderId, array $formData)` jest wywolane z danymi zamowienia #114 lub #115
|
||||
Then service buduje payload polkurier (sender z `company_settings`, receiver z order_addresses, paczka z formData), woła `PolkurierApiClient::createShipment()`, zapisuje wynik do `shipment_packages` (provider='polkurier', tracking_number, label_path po pobraniu, payload_json)
|
||||
And `downloadLabel($packageId, $storagePath)` pobiera plik etykiety i aktualizuje `shipment_packages.label_path`
|
||||
And `checkCreationStatus($packageId)` zwraca aktualny stan z `shipment_packages` (sync vs `getStatus()` API)
|
||||
And `getDeliveryServices()` zwraca cache'owana liste przewoznikow z `getAvailableCarriers()` (per-request)
|
||||
```
|
||||
|
||||
## AC-3: PolkurierTrackingService cron tracking
|
||||
```gherkin
|
||||
Given paczka z `provider='polkurier'` i `tracking_number` istnieje w `shipment_packages`
|
||||
When `ShipmentTrackingHandler` (cron) wywoluje `PolkurierTrackingService::getDeliveryStatus($package)`
|
||||
Then service woła `PolkurierApiClient::getStatus()`, parsuje surowy status i mapuje przez `delivery_status_mappings(provider='polkurier')` na znormalizowany status z `delivery_statuses`
|
||||
And zwraca `{status: <normalized>, status_raw: <polkurier_code>, description: <polkurier_label>}`
|
||||
And `supports('polkurier')` zwraca true
|
||||
```
|
||||
|
||||
## AC-4: UI prepare.php panel polkurier
|
||||
```gherkin
|
||||
Given operator wszedl na `/orders/115/shipment/prepare`
|
||||
When wybiera "polkurier" z dropdowna przewoznika
|
||||
Then JS pokazuje panel `#shipment-polkurier-panel` z dynamicznym selectem uslug (zaladowanym z `getAvailableCarriers`)
|
||||
And dla uslug `supports_pickup_point=true` pojawia sie selektor punktu odbioru z listą punktów odpowiedniego typu (InPost/Pocztex/Kurier48/ORLEN)
|
||||
And ukryty input `provider_code` ustawia sie na `polkurier`
|
||||
And submit formularza tworzy paczke przez `ShipmentController::store()` -> `ShipmentProviderRegistry::get('polkurier')->createShipment()`
|
||||
```
|
||||
|
||||
## AC-5: delivery_status_mappings + /settings/delivery-statuses
|
||||
```gherkin
|
||||
Given migracja `20260514_000115_seed_polkurier_delivery_status_mappings.sql` wykonana
|
||||
When operator otwiera `/settings/delivery-statuses` (tab 'mapping')
|
||||
Then widoczne sa wpisy `provider='polkurier'` z surowymi statusami polkuriera mapowanymi na znormalizowane statusy z `delivery_statuses` (np. `nowa_paczka` -> `registered`, `w_doreczeniu` -> `out_for_delivery`, `doreczone` -> `delivered`, `anulowane` -> `cancelled`)
|
||||
And `DeliveryStatus::trackingUrl('polkurier', $tracking, $carrierId)` zwraca poprawny link sledzenia (URL polkuriera lub bezposrednio przewoznika)
|
||||
```
|
||||
|
||||
## AC-6: Live test na zamowieniach #114 i #115
|
||||
```gherkin
|
||||
Given operator ma aktywne konto polkurier (Phase 127 test "Autoryzacja: 1")
|
||||
When operator nadaje paczki na #114 i #115 przez nowy panel UI
|
||||
Then dla obu zamowien `shipment_packages` zawiera wiersz `provider='polkurier'`, `status='created'`, niepusty `tracking_number`, sciezke `label_path` do pobranej etykiety
|
||||
And operator widzi etykiety jako PDF w `/orders/{id}` zakladka Przesylki
|
||||
And operator recznie anuluje obie paczki w panelu polkurier.pl po weryfikacji (poza zakresem kodu — manualna akcja w UI polkuriera)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: PolkurierApiClient — implementacja pelnego kontraktu API</name>
|
||||
<files>src/Modules/Settings/PolkurierApiClient.php</files>
|
||||
<action>
|
||||
Zastapic stuby `createShipment/getLabel/getStatus/cancelOrder` realnymi implementacjami i dodac `getAvailableCarriers`, `getParcelMachines`, `getPostOffices`.
|
||||
|
||||
Wspolny helper `private function call(string $apimetod, array $data, string $login, string $apiToken): array`:
|
||||
- Buduje payload `{authorization: {login, token}, apimetod, data}`.
|
||||
- Wykorzystuje istniejacy `postJson()` (Phase 127).
|
||||
- Parsuje envelope: jezeli `status === 'success'` zwraca `$decoded['response']` (array). W przeciwnym razie rzuca `RuntimeException` z trescia `response` (string albo zserializowany JSON).
|
||||
- PHP 8.5: NIE wywolywac `curl_close()`.
|
||||
|
||||
Metody publiczne:
|
||||
- `createShipment(string $login, string $apiToken, array $payload): array` — apimetod `new_order` (sprawdzic dokladna nazwe w SDK polkurier-sdk/src/Api). Zwraca `{order_id, tracking_number, raw}`.
|
||||
- `getLabel(string $login, string $apiToken, string $orderId, string $format): string` — apimetod `get_label`, format=PDF/ZPL/EPL. Zwraca surowa zawartosc base64-decoded jezeli polkurier zwraca base64; w przeciwnym razie binarny stream. Sprawdzic odpowiedz API.
|
||||
- `getStatus(string $login, string $apiToken, string $orderId): array` — apimetod `get_status`. Zwraca `{status_code, status_name, status_date, raw}`.
|
||||
- `cancelOrder(string $login, string $apiToken, string $orderId): array` — apimetod `cancel_order`. (NIE bedzie wywolywana w Phase 128 — operator anuluje w UI polkuriera, ale metoda dostepna dla przyszlych planow.)
|
||||
- `getAvailableCarriers(string $login, string $apiToken): array` — apimetod `get_available_carriers` (potwierdzic w SDK; mozliwe alternatywy: `get_carriers`, `get_services`). Zwraca liste przewoznikow.
|
||||
- `getParcelMachines(string $login, string $apiToken, string $type, ?string $postalCode = null): array` — apimetod `get_parcel_machines`. type=InPost/Pocztex/Kurier48/ORLEN.
|
||||
- `getPostOffices(string $login, string $apiToken, string $type): array` — apimetod `get_post_offices` (jezeli polkurier rozdziela).
|
||||
|
||||
UWAGA: dokladne nazwy `apimetod` zweryfikowac wzgledem `polkurier-sdk` (https://github.com/Polkurier/polkurier-sdk) — pliki `src/Api/*Api.php`. Jezeli nazwa rozna od zakladanej, dostosowac stale prywatne (`private const APIMETOD_NEW_ORDER = '...'` itp.).
|
||||
|
||||
Avoid: hardcodowane mapowanie statusow (zostawiamy `DeliveryStatusMappingRepository`), zmiany w `testConnection()` (dziala od Phase 127), wprowadzania `Content-Type: application/json; charset=UTF-8` (polkurier odrzuca — zachowac dokladnie `application/json`).
|
||||
</action>
|
||||
<verify>
|
||||
1. `php -l src/Modules/Settings/PolkurierApiClient.php` (no syntax errors).
|
||||
2. Operator wywoluje przyszly smoke test: `php bin/smoke-polkurier.php` (skrypt z Task 4) — pierwszy crash wskaze brakujace pole.
|
||||
3. Rzut `RuntimeException` zawiera tresc z pola `response` (nie `status: error`).
|
||||
</verify>
|
||||
<done>AC-1 satisfied: wszystkie 7 metod publicznych zaimplementowane wzorem `testConnection()`; envelope `{status, response}` parsowany jednolicie; bledy rzucane z trescia z `response`.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: PolkurierShipmentService + PolkurierTrackingService</name>
|
||||
<files>src/Modules/Shipments/PolkurierShipmentService.php, src/Modules/Shipments/PolkurierTrackingService.php, src/Modules/Shipments/DeliveryStatus.php</files>
|
||||
<action>
|
||||
**PolkurierShipmentService** — `final class implements ShipmentProviderInterface`, wzorzec `ApaczkaShipmentService` (1044 LOC, ale polkurier prostszy — ~500-700 LOC):
|
||||
|
||||
Konstruktor (manualny DI, mirror Apaczki):
|
||||
- `PolkurierIntegrationRepository $integrationRepository`
|
||||
- `PolkurierApiClient $apiClient`
|
||||
- `ShipmentPackageRepository $packages`
|
||||
- `CompanySettingsRepository $companySettings`
|
||||
- `OrdersRepository $ordersRepository`
|
||||
|
||||
Metody:
|
||||
- `code(): string` -> `'polkurier'`.
|
||||
- `getDeliveryServices(): array` — `getCredentials()` -> `$apiClient->getAvailableCarriers()`. Cache per-request (`private ?array $servicesCache = null`).
|
||||
- `createShipment(int $orderId, array $formData): array`:
|
||||
1. `findDetails($orderId)` z OrdersRepository; throw `ShipmentException` gdy null.
|
||||
2. `requireCredentials()` -> `[$login, $token]` z `PolkurierIntegrationRepository::getCredentials()`; throw `IntegrationConfigException` gdy null.
|
||||
3. Sender z `CompanySettingsRepository::getSenderAddress()` + walidacja (`validateSenderAddress`).
|
||||
4. Receiver z `order_addresses` (delivery type), normalizacja telefonu/postal_code.
|
||||
5. Wybor uslugi z `$formData['service_code']` lub `$formData['carrier_id']`.
|
||||
6. Wymiary/waga z formData (z domyslnymi z `company_settings.default_package_*`).
|
||||
7. Punkt odbioru: jezeli `$formData['receiver_point_id']` niepuste -> wstawiamy w payload polkuriera (klucz zaleznie od SDK).
|
||||
8. COD/insurance z formData.
|
||||
9. `$apiClient->createShipment($login, $token, $payload)` -> `{order_id, tracking_number, ...}`.
|
||||
10. `$packages->insert([...])` z `provider='polkurier'`, `command_id=order_id`, `tracking_number`, `status='created'`, `payload_json=zserializowany_payload`.
|
||||
11. Synchroniczne pobranie etykiety przez `downloadLabel($packageId, $storagePath)` — analog Apaczki, ktora pobiera label do `storage/labels/`.
|
||||
12. Zwroc `['package_id' => ..., 'tracking_number' => ..., 'label_path' => ...]`.
|
||||
- `checkCreationStatus(int $packageId): array` — fetch z `shipment_packages`; jezeli `status='draft'` -> pingnij `getStatus()` API i zaktualizuj.
|
||||
- `downloadLabel(int $packageId, string $storagePath): array` — `$apiClient->getLabel($login, $token, $orderId, $package['label_format'] ?? 'PDF')`; zapisz do `$storagePath/polkurier_{packageId}.pdf` (lub `.zpl`); update `shipment_packages.label_path`.
|
||||
|
||||
**PolkurierTrackingService** — `final class implements ShipmentTrackingInterface`:
|
||||
- `supports(string $provider): bool` -> `strtolower($provider) === 'polkurier'`.
|
||||
- `getDeliveryStatus(array $package): ?array`:
|
||||
1. `requireCredentials()`; zwroc null gdy konfiguracja nieaktywna (cron nie powinien rzucac, tylko skipowac).
|
||||
2. `$apiClient->getStatus($login, $token, $package['command_id'])`.
|
||||
3. Mapuj `status_code` przez `DeliveryStatusMappingRepository::findNormalized('polkurier', $rawStatus)`.
|
||||
4. Fallback `unknown` gdy brak mapowania (analog Apaczki).
|
||||
5. Zwroc `['status' => $normalized, 'status_raw' => $rawStatus, 'description' => $statusName]`.
|
||||
|
||||
**DeliveryStatus.php** — dolozyc obsluge providera `polkurier` w `trackingUrl(string $provider, string $tracking, string $carrierId): string`:
|
||||
- Polkurier deleguje do przewoznika docelowego — `carrierId` mowi nam ktory. Fallback URL `https://polkurier.pl/sledzenie/<tracking>` (jezeli polkurier ma taki) lub URL przewoznika docelowego z istniejacych branch-ow (`inpost`, `dpd`, etc.). MVP: zwroc URL bazowy polkurier + tracking_number.
|
||||
|
||||
Avoid:
|
||||
- Hardcodowania URL tracking polkuriera bez weryfikacji (sprawdzic w panelu polkurier lub SDK).
|
||||
- Pobierania etykiety w osobnym requeście jezeli polkurier zwraca `label_base64` w odpowiedzi createShipment (parsowac i zapisywac od razu).
|
||||
- Throwowania w `PolkurierTrackingService::getDeliveryStatus` przy braku credentials — cron musi byc odporny.
|
||||
</action>
|
||||
<verify>
|
||||
1. `php -l src/Modules/Shipments/PolkurierShipmentService.php` i `PolkurierTrackingService.php`.
|
||||
2. Manualnie: po Task 3 wejdz na `/orders/114/shipment/prepare`, wybierz polkurier — dropdown zwraca przewoznikow z API.
|
||||
</verify>
|
||||
<done>AC-2 i AC-3 satisfied: oba serwisy implementuja interfejsy, integruja z `PolkurierApiClient`, `ShipmentPackageRepository` i `DeliveryStatusMappingRepository`. Kontrakt Phase 127 (single global config, `is_active=1` guard) zachowany.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Wiring + UI prepare.php panel polkurier</name>
|
||||
<files>routes/web.php, src/Modules/Shipments/ShipmentController.php, src/Modules/Cron/CronHandlerFactory.php, resources/views/shipments/prepare.php</files>
|
||||
<action>
|
||||
**routes/web.php**:
|
||||
- W `$shipmentProviderRegistry = new ShipmentProviderRegistry([...])` (linia ~474) dolozyc `new PolkurierShipmentService($polkurierIntegrationRepository, new PolkurierApiClient(), $shipmentPackageRepository, $companySettingsRepository, new OrdersRepository(...))`.
|
||||
- Dolozyc `use App\Modules\Shipments\PolkurierShipmentService;` w nagłowku.
|
||||
|
||||
**CronHandlerFactory.php** (linia ~166-169):
|
||||
- W tablicy trackerow dolozyc `new PolkurierTrackingService($polkurierIntegrationRepository, $polkurierApiClient, $deliveryStatusMappingRepository)`.
|
||||
- Dolozyc `use App\Modules\Shipments\PolkurierTrackingService;` i `use App\Modules\Settings\PolkurierIntegrationRepository;`/`PolkurierApiClient;`.
|
||||
|
||||
**ShipmentController.php**:
|
||||
- Wstrzyknac `PolkurierShipmentService $polkurierService` (lub uzywac z registry).
|
||||
- W `prepare()` przekazac do widoku `$polkurierServices = $polkurierService->getDeliveryServices()` (try/catch — empty array on failure).
|
||||
|
||||
**resources/views/shipments/prepare.php** — wzorzec panelu Apaczki (linie 172-216):
|
||||
- Dolozyc `<option value="polkurier">polkurier</option>` do `#carrierSelect` (linia ~103).
|
||||
- Dolozyc panel `<div id="shipment-polkurier-panel">` z:
|
||||
- `<select id="shipment-polkurier-service-select">` wypelniony `$polkurierServices` (carrier_id, name).
|
||||
- Conditional pickup point picker (`<select id="shipment-polkurier-point-select">`) widoczny gdy wybrana usluga ma `supports_pickup_point=true`. Lista punktow ladowana AJAX-em z nowego endpointu `/shipments/polkurier/points?type=InPost&postal=XX-XXX` (dodac route + metoda w `ShipmentController`).
|
||||
- Hidden input `name="service_code"` aktualizowany przez JS na podstawie selecta.
|
||||
- JS (linie ~580-650): dodac `polkurierSelect`/`polkurierPanel`/`polkurierPointSelect`; toggle widocznosci paneli; ustaw `providerInput.value = 'polkurier'` gdy carrier='polkurier'.
|
||||
|
||||
Avoid:
|
||||
- Powielania logiki Apaczki — uzyc tych samych helperow JS gdzie sie da.
|
||||
- Hardcodowania listy punktow w widoku — wszystko z API polkuriera przez AJAX.
|
||||
</action>
|
||||
<verify>
|
||||
1. `/orders/114/shipment/prepare` — dropdown przewoznika ma "polkurier"; po wyborze pokazuje panel.
|
||||
2. Select uslug ma realne wartosci z polkuriera.
|
||||
3. `php -l routes/web.php` i `CronHandlerFactory.php`.
|
||||
</verify>
|
||||
<done>AC-4 satisfied: panel polkurier widoczny w prepare.php, integracja z registry, cron tracker zarejestrowany.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Live test polkurier ShipmentService na zamowieniach #114 i #115. Operator nadaje paczki przez nowy panel UI, weryfikuje wynik, recznie anuluje w panelu polkurier po weryfikacji.</what-built>
|
||||
<how-to-verify>
|
||||
1. XAMPP MySQL online + cron disabled (zeby tracking nie zaczal pingowac przed weryfikacja).
|
||||
2. Otworz `/orders/114/shipment/prepare`:
|
||||
- Wybierz "polkurier" w dropdownie.
|
||||
- Wybierz usluge kuriera (np. DPD Standard albo InPost Kurier).
|
||||
- Uzupelnij wymiary/wage; potwierdz adres odbiorcy.
|
||||
- Submit.
|
||||
3. Sprawdz redirect na `/orders/114` -> zakladka Przesylki:
|
||||
- Wiersz `provider=polkurier`, niepusty `tracking_number`, link "Pobierz etykiete" otwiera PDF.
|
||||
4. Powtorz dla zamowienia #115 — ten raz z usluga paczkomatowa (InPost Paczkomat); selektor punktu pokazuje liste paczkomatow z API.
|
||||
5. Otworz panel polkurier.pl manualnie -> zobacz utworzone paczki.
|
||||
6. Anuluj obie paczki w panelu polkurier (manualna akcja).
|
||||
7. Zglos wynik: "OK" jezeli paczki utworzone i etykiety pobrane, "issues" z opisem co nie dziala.
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" to continue with delivery_status_mappings seed, or describe issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 4: Migracja seed delivery_status_mappings + weryfikacja /settings/delivery-statuses</name>
|
||||
<files>database/migrations/20260514_000115_seed_polkurier_delivery_status_mappings.sql</files>
|
||||
<action>
|
||||
Stworzyc migracje seedujaca `delivery_status_mappings` (provider='polkurier') na podstawie REALNYCH statusow zwracanych przez API polkurier w Task 1 (operator po live tescie zna konkretne `status_code` z `getStatus`).
|
||||
|
||||
Baseline mapowan (do dostrojenia po live tescie):
|
||||
- `nowa` / `przyjete` / `oczekuje` -> `registered`
|
||||
- `wydrukowane` / `przygotowanie` -> `label_printed` (jezeli istnieje w `delivery_statuses` — sprawdzic w Phase 108)
|
||||
- `nadane` / `w_dostawie` / `w_doreczeniu` -> `in_transit` lub `out_for_delivery`
|
||||
- `doreczone` / `odebrane` -> `delivered`
|
||||
- `zwrocone` -> `returned`
|
||||
- `anulowane` -> `cancelled`
|
||||
- `niedoreczone` / `blad_doreczenia` -> `delivery_failed` lub `unknown` (zaleznie od `delivery_statuses`)
|
||||
|
||||
Migracja musi byc idempotentna: `INSERT INTO delivery_status_mappings (provider, raw_status, normalized_status, description) VALUES (...) ON DUPLICATE KEY UPDATE normalized_status = VALUES(normalized_status), description = VALUES(description);`.
|
||||
|
||||
Uruchom `php bin/migrate.php` po zatwierdzeniu mapowan.
|
||||
|
||||
Otworz `/settings/delivery-statuses?tab=mapping` -> potwierdz widocznosc wpisow provider='polkurier'.
|
||||
|
||||
Avoid:
|
||||
- Seedu na bazie przypuszczen — uzyj statusow ZAOBSERWOWANYCH w live tescie z Task checkpoint.
|
||||
- Tworzenia nowych wpisow w `delivery_statuses` (jezeli polkurier zwraca status ktorego nie ma — dodaj rownolegly INSERT do tej migracji albo osobna migracje).
|
||||
</action>
|
||||
<verify>
|
||||
1. `php bin/migrate.php` zwraca success.
|
||||
2. `SELECT COUNT(*) FROM delivery_status_mappings WHERE provider='polkurier'` >= 6.
|
||||
3. `/settings/delivery-statuses?tab=mapping` pokazuje wiersze polkurier.
|
||||
4. Ponowne uruchomienie migracji = no-op (idempotencja).
|
||||
</verify>
|
||||
<done>AC-5 satisfied: mapowania wpisane do DB, widoczne w UI, idempotentne.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 5: Aktualizacja dokumentacji codebase</name>
|
||||
<files>.paul/codebase/architecture.md, .paul/codebase/db_schema.md, .paul/codebase/tech_changelog.md</files>
|
||||
<action>
|
||||
**architecture.md** — dolozyc sekcje "Phase 128 — polkurier ShipmentService + Tracking" pod sekcja Phase 127:
|
||||
- PolkurierApiClient — pelna lista 7 publicznych metod z apimetod-ami.
|
||||
- PolkurierShipmentService — kontrakt ShipmentProviderInterface, lista pol payloadu, integracja z `getAvailableCarriers`.
|
||||
- PolkurierTrackingService — kontrakt ShipmentTrackingInterface, mapowanie statusow.
|
||||
- Wiring w `ShipmentProviderRegistry` i `CronHandlerFactory`.
|
||||
- UI prepare.php panel polkurier — selector uslug + pickup point ajax.
|
||||
|
||||
**db_schema.md** — dolozyc seed mapping rows w sekcji "delivery_status_mappings":
|
||||
- Wymienic mapowania `provider='polkurier'` z migracji.
|
||||
|
||||
**tech_changelog.md** — wpis z data 2026-05-14 (lub data wdrozenia):
|
||||
- "Phase 128 (polkurier ShipmentService): pelna implementacja API client (createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers/getParcelMachines/getPostOffices), PolkurierShipmentService implementing ShipmentProviderInterface, PolkurierTrackingService, UI panel w prepare.php, seed delivery_status_mappings."
|
||||
|
||||
Avoid: kopiowania kodu do dokumentacji (zostawic referencje sciezek + 1-2 zdania kontraktu).
|
||||
</action>
|
||||
<verify>
|
||||
Manualnie przegladnac diff `.paul/codebase/*.md` — wpisy obecne, formatowanie spojne z Phase 127.
|
||||
</verify>
|
||||
<done>Dokumentacja zaktualizowana zgodnie z CLAUDE.md (sekcja "Utrwalanie stalych wymagan").</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- `src/Modules/Settings/PolkurierIntegrationRepository.php` — Phase 127, kontrakt stabilny.
|
||||
- `src/Modules/Settings/PolkurierApiClient::testConnection()` — Phase 127, zweryfikowany na zywym koncie ("Autoryzacja: 1").
|
||||
- `apaczka_integration_settings`, `ApaczkaShipmentService`, `ApaczkaTrackingService` — Apaczka dziala niezaleznie obok polkuriera.
|
||||
- `database/migrations/20260514_000114_create_polkurier_integration_settings.sql` (Phase 127).
|
||||
- `ShipmentProviderInterface` i `ShipmentTrackingInterface` — kontrakty stabilne (nie dolozyc/zmienic metod).
|
||||
- Reszta paneli w `prepare.php` (allegro/inpost/apaczka) — tylko dolozenie nowego panelu polkurier obok.
|
||||
|
||||
## SCOPE LIMITS
|
||||
- BEZ implementacji `OrderValuationV2` (wycena przed nadaniem) — odlozone na osobna faze.
|
||||
- BEZ presetow przesylek dla polkuriera (`shipment_presets.provider_code='polkurier'`) — operator moze ich uzywac dopiero jak panel polkurier dziala; presety w osobnej fazie.
|
||||
- BEZ widoku CLI smoke test scriptu (`bin/smoke-polkurier.php`) — testujemy w realnym UI na #114/#115.
|
||||
- BEZ event automatyzacji `shipment.created` zmian — to zdarzenie juz emitowane jednolicie z `ShipmentController::store()` dla wszystkich providerow.
|
||||
- BEZ idempotencji createShipment (double-POST guard) — jak w Apaczce, brak retry guard w MVP.
|
||||
- BEZ refaktoringu wspolnego kodu Apaczka/polkurier (`buildReceiverAddress`, `validateSenderAddress` itp.) — kopiujemy wzorzec, deduplikacja w osobnym planie.
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] `php -l` przechodzi dla wszystkich zmienionych plikow PHP
|
||||
- [ ] Migracja `20260514_000115_*.sql` wykonana, ponowne uruchomienie = no-op
|
||||
- [ ] Operator potwierdzil checkpoint: 2 paczki utworzone na #114 i #115, etykiety pobrane, manualnie anulowane w panelu polkurier
|
||||
- [ ] `/settings/delivery-statuses?tab=mapping` pokazuje wpisy provider='polkurier'
|
||||
- [ ] `/orders/{id}/shipment/prepare` panel polkurier widoczny i funkcjonalny
|
||||
- [ ] Cron tracking nie crashuje (sprawdzic `storage/logs/app.log` po jednym przebiegu)
|
||||
- [ ] Dokumentacja `.paul/codebase/*.md` zaktualizowana
|
||||
- [ ] Wszystkie AC-1..AC-6 spelnione
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Operator moze nadawac paczki przez polkurier z poziomu `/orders/{id}/shipment/prepare` w 4 trybach: kurier door-to-door, paczkomat InPost, punkt Pocztex/Kurier48, ORLEN.
|
||||
- Tracking polkurier dziala w cronie i aktualizuje `shipment_packages.delivery_status` przez `delivery_status_mappings`.
|
||||
- Live test na #114 i #115 zakonczony sukcesem (paczki utworzone, etykiety pobrane, recznie anulowane).
|
||||
- Zaden istniejacy provider (Apaczka/InPost/Allegro WZA) nie regresuje.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/128-polkurier-shipment-service/128-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user