Files
orderPRO/.paul/phases/127-polkurier-integration-foundation/127-01-PLAN.md
Jacek Pyziak 3443879f59 feat(127): polkurier integration foundation
Single-instance globalna konfiguracja polkurier.pl jako alternatywa
dla Apaczki: szyfrowany login + Token API, karta w hubie integracji
i realny test polaczenia przez apimetod=test_auth_api zweryfikowany
na zywym koncie operatora (Autoryzacja: 1).

ShipmentProviderRegistry netkniety - PolkurierShipmentService/
TrackingService w kolejnych fazach.

Kluczowe ustalenia kontraktu API (z SDK polkurier-sdk):
- POST https://api.polkurier.pl/ (jeden endpoint)
- JSON body: {authorization:{login,token}, apimetod, data}
- Sukces: top-level status === 'success' (nie 'ok')
- Blad: tresc w polu 'response' envelope'a
- Content-Type: application/json (strict, bez charset suffix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:43:11 +02:00

20 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, delegation
phase plan type wave depends_on files_modified autonomous delegation
127-polkurier-integration-foundation 01 execute 1
database/migrations/20260514_000114_create_polkurier_integration_settings.sql
src/Modules/Settings/PolkurierIntegrationRepository.php
src/Modules/Settings/PolkurierApiClient.php
src/Modules/Settings/PolkurierIntegrationController.php
src/Modules/Settings/IntegrationsHubController.php
resources/views/settings/integrations/polkurier.php
routes/web.php
.paul/codebase/db_schema.md
.paul/codebase/architecture.md
.paul/codebase/tech_changelog.md
true auto
## Goal Dodac fundament integracji z brokerem kurierskim polkurier.pl jako rownolegla alternatywe dla Apaczki: pojedyncza globalna konfiguracja w `/settings/integrations/polkurier` (szyfrowany Token API), karta w hubie integracji `/settings/integrations`, oraz realny test polaczenia z API polkuriera (wywolanie endpointu zwracajacego dane konta lub liste uslug).

Purpose

Operator dostaje druga bramke kurierska oprocz Apaczki. Faza zamyka warstwe ustawien i testu polaczenia — tworzenie przesylek, etykiety, tracking i mapowania metod dostawy beda dolozone w kolejnych fazach (analogicznie do tego jak Phase 116/117 zamknely tylko ustawienia HostedSMS/SMSPLANET przed pelnym SMS-em). Apaczka i jej ShipmentProviderInterface zostaja niezmienione — polkurier dziala obok.

Output

  • Migracja DDL tworzaca polkurier_integration_settings (mirror apaczka_integration_settings).
  • PolkurierIntegrationRepository szyfrujacy Token API przez IntegrationSecretCipher i zarzadzajacy pojedynczym rekordem integrations.type='polkurier' (id rekordu zalezne, nie wpisywane na sztywno).
  • PolkurierApiClient realnie wywolujacy API polkuriera w trybie test (endpoint zwracajacy dane konta / liste uslug — wybor zgodnie z dokumentacja SDK ze strony bazy wiedzy polkuriera, decyzja na czas implementacji).
  • PolkurierIntegrationController z routami GET /settings/integrations/polkurier, POST .../save, POST .../test.
  • Wiersz "polkurier" w hubie /settings/integrations ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu.
  • Aktualizacja dokumentow projektowych (db_schema, architecture, tech_changelog).
## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md

Prior Art (wzorzec do skopiowania)

@src/Modules/Settings/ApaczkaIntegrationRepository.php @src/Modules/Settings/ApaczkaApiClient.php @src/Modules/Settings/ApaczkaIntegrationController.php @src/Modules/Settings/HostedSmsIntegrationRepository.php @src/Modules/Settings/HostedSmsIntegrationController.php @src/Modules/Settings/SmsplanetIntegrationRepository.php @src/Modules/Settings/SmsplanetIntegrationController.php @src/Modules/Settings/IntegrationsHubController.php @src/Modules/Settings/IntegrationSecretCipher.php @database/migrations/20260512_000109_consolidate_fakturownia_to_single_instance.sql

Codebase docs

@.paul/codebase/architecture.md @.paul/codebase/db_schema.md

Routy i widok wzorcowe

@routes/web.php @resources/views/settings/integrations/apaczka.php

- **Zakres MVP** — Jaki zakres ma pokryc pierwsza faza integracji polkurier.pl? - Odpowiedz: Tylko fundament + test polaczenia (wzorzec faz 116/117). - **Model konta** — Pojedyncza globalna instancja czy wieloinstancyjna? - Odpowiedz: Single instance (jak Apaczka/InPost) — fixed `polkurier_integration_settings.id=1`, jeden rekord `integrations.type='polkurier'`. - **Apaczka vs polkurier** — Zastapienie czy rownoleglosc? - Odpowiedz: Obok Apaczki — oba dostawcy dzialaja, Apaczka netknieta, operator wybiera w kolejnych fazach (gdy `PolkurierShipmentService` zostanie dodany). - **Paczkomaty / punkty odbioru** — Czy w tej fazie? - Odpowiedz: Poza zakresem tej fazy. Operator potwierdzil "ma dzialac jak Apaczka"; obsluga punktow odbioru pojawi sie razem z `PolkurierShipmentService` w nastepnej fazie (tak jak Apaczka — receiver_point_id w shipment_packages).

<acceptance_criteria>

AC-1: Migracja tworzy single-instance tabele konfiguracji

Given XAMPP MySQL jest online i migracje sa zacommitowane
When operator uruchamia `php bin/migrate.php`
Then powstaje tabela `polkurier_integration_settings` z kolumnami: `id TINYINT UNSIGNED PK` (always 1), `integration_id INT UNSIGNED UNIQUE NULL FK -> integrations(id) CASCADE`, `api_token_encrypted TEXT NULL`, `environment ENUM('production','sandbox') NOT NULL DEFAULT 'production'`, `default_label_format VARCHAR(8) NOT NULL DEFAULT 'PDF'`, `created_at`, `updated_at`
And ponowne uruchomienie migracji jest no-op (`CREATE TABLE IF NOT EXISTS`)

AC-2: Repozytorium szyfruje Token API i zarzadza pojedynczym rekordem integrations

Given migracja AC-1 wykonana
When operator zapisuje konfiguracje przez `PolkurierIntegrationRepository::saveSettings([api_token => 'XYZ', environment => 'production', is_active => 1])`
Then w `integrations` powstaje (lub zostaje zaktualizowany) jeden rekord `type='polkurier'`, `polkurier_integration_settings.id=1` ma uzupelnione `integration_id` i zaszyfrowane `api_token_encrypted`
And `getSettings()` zwraca rekord BEZ surowego tokena, jedynie z flaga `has_api_token: bool`
And `getCredentials()` zwraca odszyfrowany Token API tylko gdy konfiguracja jest kompletna i aktywna (`is_active=1`)

AC-3: Endpoint testowy realnie wywoluje API polkuriera i zapisuje wynik

Given operator zapisal poprawny Token API
When operator klika "Testuj polaczenie" w `/settings/integrations/polkurier`
Then `PolkurierApiClient` wykonuje realne wywolanie HTTP do API polkuriera (endpoint nie pisany na sztywno w PLAN, wybierany przez implementatora z dokumentacji `Polkurier_WebService_API_1_1.pdf` — preferowany endpoint typu "lista uslug" / "konto" zwracajacy dane bez tworzenia przesylki)
And `integrations.last_test_status / last_test_http_code / last_test_message / last_test_at` zostaja zaktualizowane przez `IntegrationsRepository::updateTestResult()`
And UI pokazuje czytelny komunikat (sukces albo blad z opisem) — bez surowego dump-u JSON/XML
And brak zaszyfrowanego tokena w logach (`storage/logs/app.log` nie zawiera plaintext tokena nawet w przypadku bledu API)

AC-4: Karta polkurier w hubie integracji

Given konfiguracja istnieje (kompletna albo niekompletna)
When operator otwiera `/settings/integrations`
Then widzi wiersz "polkurier" z statusem: skonfigurowana (tak/nie), token zapisany (tak/nie), aktywna (tak/nie), ostatni test (timestamp + ok/error)
And klikniecie wiersza prowadzi do `/settings/integrations/polkurier`

AC-5: Apaczka i istniejace ShipmentProviderRegistry netkniete

Given Apaczka jest aktywna i zarejestrowana w `ShipmentProviderRegistry`
When polkurier zostaje dodany do hubu integracji
Then `ShipmentProviderRegistry` NIE rejestruje polkuriera (brak `PolkurierShipmentService` w tej fazie)
And tworzenie przesylek Apaczka dziala bez zmian
And `routes/web.php` nie modyfikuje wiring ApaczkaShipmentService/Tracking

AC-6: Dokumentacja zaktualizowana

Given plan ukonczony
When operator otwiera `.paul/codebase/db_schema.md`
Then sekcja Integrations zawiera definicje `polkurier_integration_settings`
And `.paul/codebase/architecture.md` zawiera sekcje "Phase 127 - polkurier Integration Settings" z opisem repository, api client, controller, hub
And `.paul/codebase/tech_changelog.md` zawiera wpis chronologiczny z data i opisem co + dlaczego

</acceptance_criteria>

Task 1: Migracja DB + PolkurierIntegrationRepository database/migrations/20260514_000114_create_polkurier_integration_settings.sql, src/Modules/Settings/PolkurierIntegrationRepository.php 1) Migracja DDL `CREATE TABLE IF NOT EXISTS polkurier_integration_settings` zgodna z AC-1 (mirror `apaczka_integration_settings` + analogia do `hostedsms_integration_settings`/`smsplanet_integration_settings` w zakresie ENUM environment). InnoDB, utf8mb4_unicode_ci. FK `integration_id REFERENCES integrations(id) ON DELETE CASCADE`. Migracja MUSI byc idempotentna (re-run = no-op zgodnie z decyzja projektu: nigdy `SELECT 1;`, tylko DDL — patrz decision 2026-05-10 w PROJECT.md). 2) `PolkurierIntegrationRepository final class` w `src/Modules/Settings/`: - konstruktor `(Medoo $db, IntegrationSecretCipher $cipher, IntegrationsRepository $integrations)`, - `getSettings(): array` — JOIN `integrations` z `polkurier_integration_settings` po `integration_id`, zwraca `has_api_token: bool` (NIE plaintext), `environment`, `default_label_format`, `is_active`, `last_test_*`, - `saveSettings(array $payload): void` — upsert: gdy brak rekordu `integrations.type='polkurier'`, twórz przez `IntegrationsRepository::ensureIntegration('polkurier', $name)`; gdy token jest pustym stringiem -> nie nadpisuj (BC z patternem fakturowni); inaczej zaszyfruj przez `IntegrationSecretCipher::encrypt()`. Walidacja serwerowa wymaganych pol. - `getCredentials(): ?array` — zwraca `['api_token' => string, 'environment' => string, 'integration_id' => int]` TYLKO gdy `is_active=1` AND `api_token_encrypted IS NOT NULL`; inaczej `null`. Uzywany przez `PolkurierApiClient` i przyszly `PolkurierShipmentService`. - `getIntegrationId(): ?int` — single source of truth dla przyszlych integracji (analogicznie do `FakturowniaIntegrationRepository`).
Avoid: tworzenia drugiego rekordu `integrations.type='polkurier'` (analogicznie do migracji konsolidacyjnej Fakturowni 20260512_000109 — single instance jest twardym kontraktem); pisania tokenu plaintext do logow; sklejania SQL stringiem (Medoo + prepared statements only).
`php bin/migrate.php` -> brak bledow, `SHOW CREATE TABLE polkurier_integration_settings;` -> kolumny zgodne z AC-1. `php -r "require 'bootstrap/app.php'; $r = $app->make(PolkurierIntegrationRepository::class); var_dump($r->getSettings());"` -> array z `has_api_token=false`. AC-1 satisfied (migracja DDL idempotentna), AC-2 satisfied (repozytorium szyfruje token, zwraca has_api_token bool, getCredentials gating na is_active). Task 2: PolkurierApiClient + Controller + widok formularza src/Modules/Settings/PolkurierApiClient.php, src/Modules/Settings/PolkurierIntegrationController.php, resources/views/settings/integrations/polkurier.php, routes/web.php 1) `PolkurierApiClient final class` w `src/Modules/Settings/`: - cURL klient z `SslCertificateResolver::resolve()` (zgodnie z patternem `FakturowniaApiClient`, `HostedSmsApiClient`, `SmsplanetApiClient`), - PHP 8.5: ZAKAZ `curl_close()` (decision 2026-05-10 — wycieka `Deprecated` HTML przed JSON response), - metoda `testConnection(string $apiToken, string $environment): array` zwracajaca `['ok' => bool, 'http_code' => int, 'message' => string]`, - WYBOR endpointu testowego: implementator MUSI sprawdzic `Polkurier_WebService_API_1_1.pdf` (link w opisie API polkuriera, baza wiedzy artykul "interfejs api do pobrania") i wybrac endpoint nie tworzacy przesylki (preferencja: "lista uslug" / "dane konta" / "wycena testowa"). Wybor udokumentowac w naglowku klasy. - przyszle stuby `createShipment()`, `downloadLabel()`, `trackShipment()`, `cancelShipment()` — rzucajace `RuntimeException("Not implemented in Phase 127")`; dolozone w kolejnych fazach.
2) `PolkurierIntegrationController final class` (mirror `HostedSmsIntegrationController` 1:1):
   - routes: `GET /settings/integrations/polkurier` (`edit`), `POST /settings/integrations/polkurier/save` (CSRF `_token`), `POST /settings/integrations/polkurier/test` (CSRF `_token`),
   - `test()` -> walidacja zapisanej konfiguracji -> `PolkurierApiClient::testConnection()` -> `IntegrationsRepository::updateTestResult()` -> Flash `Flash::push('success'|'danger', ...)` (Phase 120 pattern, NIE `Flash::set('polkurier.test')`),
   - redirect przez `RedirectPathResolver`.

3) Widok `resources/views/settings/integrations/polkurier.php`:
   - dziedziczy z `layouts/app.php`,
   - formularz: token API (password input z placeholderem "Pozostaw puste aby nie zmieniac" gdy `has_api_token=true`), environment (select production/sandbox), domyslny format etykiety (PDF/ZPL/EPL), checkbox `is_active`, przycisk "Zapisz" i osobny "Testuj polaczenie",
   - `_token` na obu formularzach (CSRF, nie `_csrf_token` — decision 2026-03-13),
   - alerty wylacznie przez komponent `resources/views/components/alert.php` (Phase 120 contract — NIE inline `<div class="alert alert--*">`),
   - potwierdzenia akcji destrukcyjnych (na przyszlosc) przez `window.OrderProAlerts.confirm({...})` options-object API (decision Phase 114/120),
   - bez inline `<style>` — style przez `resources/scss/modules/_integrations.scss` jezeli czegokolwiek brakuje (CLAUDE.md: zero CSS w widokach).

4) `routes/web.php` — wpiac controller w sekcji Settings/Integrations (po Apaczka, przed HostedSMS dla porzadku alfabetycznego). DI: `new PolkurierIntegrationController($template, $translator, $auth, new PolkurierIntegrationRepository($app->db(), $cipher, $integrationsRepository), new PolkurierApiClient($timeoutSeconds = 15))`.

Avoid: kopiowania `apaczka.php` bez przegladu (Apaczka ma duzy formularz z domyslnymi wymiarami — niepotrzebne w MVP polkuriera); dodawania `PolkurierShipmentService` do `ShipmentProviderRegistry` (out of scope, AC-5 wymaga netknietego registry); modyfikacji `apaczka_integration_settings` lub `IntegrationsRepository` poza `updateTestResult()` (reuse, nie refactor).
Build PHP `php -l src/Modules/Settings/PolkurierApiClient.php` i `php -l src/Modules/Settings/PolkurierIntegrationController.php` -> No syntax errors. Recznie: zaloguj sie, otworz `/settings/integrations/polkurier` -> formularz renderuje sie bez bledow, alerty stylowane. Zapis pustego tokenu -> blad walidacji. Zapis prawdziwego tokenu (dev konto polkurier jezeli operator ma) -> rekord w DB ma niepuste `api_token_encrypted`. Klik "Testuj polaczenie" z prawdziwym tokenem -> `integrations.last_test_status='ok'` w DB; bledny token -> `last_test_status='error'` + zrozumialy komunikat w UI. AC-3 satisfied (realne wywolanie API, zapis wyniku, brak plaintext tokena w logach); fundament UI/wiring na miejscu. Task 3: Hub integracji + aktualizacja dokumentow projektowych src/Modules/Settings/IntegrationsHubController.php, resources/views/settings/integrations/index.php, .paul/codebase/db_schema.md, .paul/codebase/architecture.md, .paul/codebase/tech_changelog.md 1) `IntegrationsHubController`: - dodaj parametr konstruktora `PolkurierIntegrationRepository $polkurier`, - dodaj metode `buildPolkurierRow(): array` zwracajaca te same klucze co `buildApaczkaRow()` (`name`, `configured`, `has_secret`, `is_active`, `last_test_status`, `last_test_at`, `last_test_message`, `link`), - wcisniecie wiersza do listy w `index()` (kolejnosc: po Apaczka, przed Allegro/inni — sprawdz aktualny porzadek w `index.php`), - `routes/web.php` - rozszerzyc wiring kontrolera o instancje `PolkurierIntegrationRepository` (kompatybilnie z istniejacymi 5+ params).
2) `resources/views/settings/integrations/index.php`:
   - jezeli widok generuje wiersze z tablicy zwracanej przez controller, ZADNA zmiana widoku nie jest potrzebna,
   - jezeli widok ma hardkodowana liste rzedow (sprawdz przed edycja) — dodaj wiersz polkurier w tym samym wzorcu co Apaczka.

3) `.paul/codebase/db_schema.md` — sekcja Integrations, po `apaczka_integration_settings`:
   - dodac pelna tabele kolumn `polkurier_integration_settings` (zgodnie z AC-1),
   - oznaczenie `(Phase 127; fixed 1 row)` w naglowku tabeli,
   - update licznika `Total tables: 62` i `Updated: 2026-05-14`.

4) `.paul/codebase/architecture.md`:
   - dodac sekcje `## Phase 127 — polkurier Integration Settings` po `## Phase 117 - SMSPLANET Integration Settings`,
   - opis `PolkurierIntegrationRepository`, `PolkurierApiClient`, `PolkurierIntegrationController`, integracja z `IntegrationsHubController`,
   - zaznaczyc ze `ShipmentProviderRegistry` nie zostal zmodyfikowany (deferred do osobnej fazy).

5) `.paul/codebase/tech_changelog.md` — dopisac wpis chronologiczny z data 2026-05-14:
   - co: fundament polkurier integration (settings + test polaczenia),
   - dlaczego: alternatywa dla Apaczki na zyczenie operatora,
   - referencja do Phase 127.

Avoid: dotykania PROJECT.md (Decisions / Validated Requirements) — to robi UNIFY, nie APPLY; modyfikacji ROADMAP.md (robi to /paul:plan w step update_state); zmiany schematu innych tabel.
`/settings/integrations` -> widac wiersz polkurier z prawidlowym statusem konfiguracji. `grep -c "polkurier_integration_settings" .paul/codebase/db_schema.md` -> co najmniej 1 trafienie. `grep -c "Phase 127" .paul/codebase/architecture.md` -> co najmniej 1 trafienie. Apaczka wiersz w hubie nadal sie renderuje (regresja zero — AC-5). AC-4 satisfied (karta polkurier w hubie), AC-5 satisfied (Apaczka netknieta), AC-6 satisfied (dokumenty zaktualizowane).

DO NOT CHANGE

  • src/Modules/Shipments/* — caly modul Shipments (registries, ApaczkaShipmentService, InpostShipmentService, ShipmentController). PolkurierShipmentService to osobna faza.
  • src/Modules/Settings/Apaczka*.php — Apaczka netknieta.
  • database/migrations/* istniejace pliki — tylko nowa migracja 20260514_000114_*.
  • src/Modules/Settings/IntegrationSecretCipher.php — reuse, zero refaktoru.
  • routes/web.php istniejace routy — tylko dodajemy 3 nowe (polkurier GET/save/test).
  • resources/views/components/alert.php — Phase 120 contract, zero modyfikacji.

SCOPE LIMITS

  • Brak PolkurierShipmentService (tworzenie przesylki) — kolejna faza.
  • Brak PolkurierTrackingService (delivery polling) — kolejna faza po Shipment.
  • Brak wpisow w delivery_status_mappings (provider='polkurier') — wymagaja realnego API tracking, do osobnej fazy.
  • Brak mapowan metod dostawy w UI (order_delivery_method -> polkurier service) — wymagaja modelowania w osobnej fazie po analizie listy uslug API polkuriera.
  • Brak zmian w shipment_presets schemacie ani UI presetow — presety beda potem.
  • Brak migracji konsolidujacych z Apaczka — oba dostawcy zyja niezaleznie.
  • Brak ShipmentProviderRegistry::register('polkurier', ...) — out of scope.
  • Brak .env / app_settings flag globalnych — token siedzi tylko w polkurier_integration_settings (jak Apaczka/HostedSMS/SMSPLANET).
Before declaring plan complete: - [ ] `php bin/migrate.php` przeszla bez bledow (operator manualnie po wdrozeniu — XAMPP online). - [ ] `php -l` przeszedl dla wszystkich nowych plikow PHP (bez syntax errors). - [ ] `/settings/integrations` renderuje wiersz polkurier obok Apaczki. - [ ] `/settings/integrations/polkurier` formularz dziala: zapis, ponowne wczytanie, "Testuj polaczenie" zwraca rzeczywista odpowiedz API (operator wpisuje prawdziwy token). - [ ] Apaczka konfiguracja `/settings/integrations/apaczka` dziala bez regresji. - [ ] `ShipmentProviderRegistry` nie zna polkuriera (grep brak `polkurier` w `src/Modules/Shipments/ShipmentProviderRegistry.php`). - [ ] Wszystkie acceptance criteria spelnione.

<success_criteria>

  • Single-instance globalna konfiguracja polkurier zapisuje sie i odczytuje (Token zaszyfrowany, has_api_token flag w UI).
  • Realne wywolanie API polkuriera w trybie test zwraca status (ok/error) i jest widoczne w hubie i panelu integracji.
  • Apaczka dziala bez regresji obok polkuriera.
  • Dokumentacja codebase (db_schema.md, architecture.md, tech_changelog.md) zaktualizowana.
  • Zaden plik z boundaries.DO NOT CHANGE nie zostal zmodyfikowany. </success_criteria>
After completion, create `.paul/phases/127-polkurier-integration-foundation/127-01-SUMMARY.md`.