feat(132): erli hardening observability docs

Phase 132 complete:

- unify Erli inbox import diagnostics and ACK failure handling

- add unit coverage for ACK safety and status push diagnostics

- document Erli observability without schema changes

Co-Authored-By: OpenAI Codex <noreply@openai.com>
This commit is contained in:
2026-05-16 15:53:03 +02:00
parent f9792a4a3f
commit 8f4745400e
12 changed files with 633 additions and 33 deletions

View File

@@ -13,8 +13,8 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
| Attribute | Value | | Attribute | Value |
|-----------|-------| |-----------|-------|
| Version | 3.8.0-dev | | Version | 3.8.0-dev |
| Status | v3.8 Erli Marketplace Integration in progress — Phase 131 shipped (Erli tracking + automation hooks); Phase 132 next | | Status | v3.8 Erli Marketplace Integration complete in code — Phases 127-132 shipped; live Erli smoke/migrations remain operator follow-up |
| Last Updated | 2026-05-16 (Phase 131 closed) | | Last Updated | 2026-05-16 (Phase 132 closed) |
## Requirements ## Requirements
@@ -131,6 +131,7 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
- [x] Mapowanie i synchronizacja statusow Erli: osobne pull/push mappings, discovery statusow z inboxa, reczny-only push `PATCH /orders/{id}/status`, cron `erli_status_sync` i zakladki w ustawieniach Erli — Phase 129 - [x] Mapowanie i synchronizacja statusow Erli: osobne pull/push mappings, discovery statusow z inboxa, reczny-only push `PATCH /orders/{id}/status`, cron `erli_status_sync` i zakladki w ustawieniach Erli — Phase 129
- [x] Przesylki Erli: zakladka mapowania dostaw, etykiety przez lokalne providery InPost/Apaczka i rejestracja paczek zewnetrznych w Erli przez `POST /shipping/external` — Phase 130 - [x] Przesylki Erli: zakladka mapowania dostaw, etykiety przez lokalne providery InPost/Apaczka i rejestracja paczek zewnetrznych w Erli przez `POST /shipping/external` — Phase 130
- [x] Tracking i automatyzacje Erli: lokalny provider tracking jak w Allegro, retry niekrytycznej rejestracji paczki zewnetrznej Erli z `shipment_tracking_sync`, wspolny kontekst `shipment.created`/`shipment.status_changed` dla regul e-mail/SMS/statystyk — Phase 131 - [x] Tracking i automatyzacje Erli: lokalny provider tracking jak w Allegro, retry niekrytycznej rejestracji paczki zewnetrznej Erli z `shipment_tracking_sync`, wspolny kontekst `shipment.created`/`shipment.status_changed` dla regul e-mail/SMS/statystyk — Phase 131
- [x] Hardening Erli: spojna diagnostyka importu/ACK w `integration_order_sync_state.last_error`, brak ACK po blednym batchu, testy jednostkowe import/status sync i dokumentacja obserwowalnosci bez nowej migracji — Phase 132
- [x] Integracja polkurier.pl (fundament): pojedyncza globalna konfiguracja w `/settings/integrations/polkurier`, szyfrowany Token API + login, karta w hubie integracji obok Apaczki i realny test polaczenia przez `apimetod=test_auth_api` zweryfikowany na zywym koncie operatora; `ShipmentProviderRegistry` netkniety — `PolkurierShipmentService/TrackingService` w kolejnych fazach — Phase 127 - [x] Integracja polkurier.pl (fundament): pojedyncza globalna konfiguracja w `/settings/integrations/polkurier`, szyfrowany Token API + login, karta w hubie integracji obok Apaczki i realny test polaczenia przez `apimetod=test_auth_api` zweryfikowany na zywym koncie operatora; `ShipmentProviderRegistry` netkniety — `PolkurierShipmentService/TrackingService` w kolejnych fazach — Phase 127
- [x] polkurier ShipmentService + TrackingService + UI prepare panel: pelen kontrakt API (createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers), `PolkurierShipmentService` implementujacy `ShipmentProviderInterface` z normalizacja shipmenttype (lowercase) i splitem ulicy na street/housenumber/flatnumber, `PolkurierTrackingService` mapujacy statusy O/P/A/WP/D/Z/W na znormalizowane, panel "polkurier" w `prepare.php` z dynamiczna lista uslug z `available_carriers`, seed migracja `delivery_status_mappings(provider='polkurier')` z 7 wpisami z PDF v1.11; live test na #114/#115 zakonczony sukcesem po 4 iteracjach (ReferenceError → uppercase shipmenttype → orderno parsing → A4/A6); rozmiar etykiety sterowany w panelu klienta polkurier.pl (Ustawienia konta → Preferencje etykiet), NIE przez API — Phase 128 - [x] polkurier ShipmentService + TrackingService + UI prepare panel: pelen kontrakt API (createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers), `PolkurierShipmentService` implementujacy `ShipmentProviderInterface` z normalizacja shipmenttype (lowercase) i splitem ulicy na street/housenumber/flatnumber, `PolkurierTrackingService` mapujacy statusy O/P/A/WP/D/Z/W na znormalizowane, panel "polkurier" w `prepare.php` z dynamiczna lista uslug z `available_carriers`, seed migracja `delivery_status_mappings(provider='polkurier')` z 7 wpisami z PDF v1.11; live test na #114/#115 zakonczony sukcesem po 4 iteracjach (ReferenceError → uppercase shipmenttype → orderno parsing → A4/A6); rozmiar etykiety sterowany w panelu klienta polkurier.pl (Ustawienia konta → Preferencje etykiet), NIE przez API — Phase 128
- [x] Order User Notes module (Phase 129): pelen CRUD notatek autorskich operatora per zamowienie. Reuse `order_notes` przez nowy `note_type='user'` z `user_id` (FK→users SET NULL) + `author_name` (snapshot) + indeks `idx_order_notes_type_order`. `OrderNotesService` z autoryzacja DB-level (`WHERE user_id = :user_id`, rowCount=0 ⇒ 403). Sekcja `#notes` w "Wiadomosci i zalaczniki" w `/orders/{id}` z inline edit form + delete przez `OrderProAlerts.confirm`. Badge `[N]` (indigo neutralny) przy nr zamowienia na `/orders/list` (subquery `user_notes_count` w paginate). Brak admin override (brak systemu rol w aplikacji) — edit/delete tylko dla autora — Phase 129 - [x] Order User Notes module (Phase 129): pelen CRUD notatek autorskich operatora per zamowienie. Reuse `order_notes` przez nowy `note_type='user'` z `user_id` (FK→users SET NULL) + `author_name` (snapshot) + indeks `idx_order_notes_type_order`. `OrderNotesService` z autoryzacja DB-level (`WHERE user_id = :user_id`, rowCount=0 ⇒ 403). Sekcja `#notes` w "Wiadomosci i zalaczniki" w `/orders/{id}` z inline edit form + delete przez `OrderProAlerts.confirm`. Badge `[N]` (indigo neutralny) przy nr zamowienia na `/orders/list` (subquery `user_notes_count` w paginate). Brak admin override (brak systemu rol w aplikacji) — edit/delete tylko dla autora — Phase 129
@@ -143,11 +144,10 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
### Active (In Progress) ### Active (In Progress)
- [ ] v3.8 Erli Marketplace Integration — Phase 132 next: hardening, observability and docs. - [ ] Next milestone selection pending after v3.8 Erli closure.
### Planned (Next) ### Planned (Next)
- [ ] Erli hardening, observability + docs — Phase 132
- [ ] ZarzÄ…dzanie produktami - [ ] ZarzÄ…dzanie produktami
- [ ] ZarzÄ…dzanie stanami magazynowymi - [ ] ZarzÄ…dzanie stanami magazynowymi
- [ ] Mobile Orders List / Mobile Order Details / Mobile Settings — pelna wersja mobilna pozostalych ekranow - [ ] Mobile Orders List / Mobile Order Details / Mobile Settings — pelna wersja mobilna pozostalych ekranow
@@ -258,6 +258,7 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
| Erli settings korzysta z zakladek Integracja/Statusy/Ustawienia | Po dodaniu mapowan strona wymagala parytetu UX z Allegro/shopPRO | 2026-05-16 | Active | | Erli settings korzysta z zakladek Integracja/Statusy/Ustawienia | Po dodaniu mapowan strona wymagala parytetu UX z Allegro/shopPRO | 2026-05-16 | Active |
| Erli etykiety uzywaja lokalnych providerow, a Erli dostaje paczke zewnetrzna przez `POST /shipping/external` | Operator nie chce nadawac na umowie Erli; API wspiera zewnetrzne paczki/tracking | 2026-05-16 | Active | | Erli etykiety uzywaja lokalnych providerow, a Erli dostaje paczke zewnetrzna przez `POST /shipping/external` | Operator nie chce nadawac na umowie Erli; API wspiera zewnetrzne paczki/tracking | 2026-05-16 | Active |
| `carrier_delivery_method_mappings` przechowuje `source_vendor_code`/`source_service_id` dla Erli | Vendor Erli i lokalny provider to osobne kontrakty, nie nalezy ich mieszac w polach Apaczki/InPost | 2026-05-16 | Active | | `carrier_delivery_method_mappings` przechowuje `source_vendor_code`/`source_service_id` dla Erli | Vendor Erli i lokalny provider to osobne kontrakty, nie nalezy ich mieszac w polach Apaczki/InPost | 2026-05-16 | Active |
| Erli hardening uzywa istniejacych powierzchni obserwowalnosci zamiast nowej tabeli logow | Operator wybral ujednolicenie istniejacych miejsc; `integration_order_sync_state.last_error`, wynik crona i activity log wystarczaja dla Phase 132 | 2026-05-16 | Active |
| polkurier startuje jako jedna globalna konfiguracja (single-instance, mirror Apaczka/HostedSMS/SMSPLANET) z realnym testowym wywolaniem `apimetod=test_auth_api` | Operator ma jedno konto polkurier; fundament musi byc zweryfikowany na zywym API zanim dolozymy `PolkurierShipmentService` | 2026-05-14 | Active | | polkurier startuje jako jedna globalna konfiguracja (single-instance, mirror Apaczka/HostedSMS/SMSPLANET) z realnym testowym wywolaniem `apimetod=test_auth_api` | Operator ma jedno konto polkurier; fundament musi byc zweryfikowany na zywym API zanim dolozymy `PolkurierShipmentService` | 2026-05-14 | Active |
| polkurier wymaga `login + token` razem w body `authorization` (nie samego tokena) | Zweryfikowane w SDK polkurier-sdk (`Auth.php`/`Request.php`); kolumna `login VARCHAR(190)` w `polkurier_integration_settings` mimo ze PLAN tego nie wymagal — kontrakt API to dyktuje | 2026-05-14 | Active | | polkurier wymaga `login + token` razem w body `authorization` (nie samego tokena) | Zweryfikowane w SDK polkurier-sdk (`Auth.php`/`Request.php`); kolumna `login VARCHAR(190)` w `polkurier_integration_settings` mimo ze PLAN tego nie wymagal — kontrakt API to dyktuje | 2026-05-14 | Active |
| polkurier API: top-level `status` === `'success'` (nie `'ok'`), tresc bledu w polu `response` envelope'a | `ResponseStatus::SUCCESS = 'success'` z `src/Type/ResponseStatus.php` SDK; bledy rzucane przez `ErrorException($response->get('response'))` w `PolkurierWebService.php`. Pattern dla wszystkich przyszlych metod polkurier API (`createShipment`, `getLabel`, `getStatus`, `cancelOrder`, etc.) | 2026-05-14 | Active | | polkurier API: top-level `status` === `'success'` (nie `'ok'`), tresc bledu w polu `response` envelope'a | `ResponseStatus::SUCCESS = 'success'` z `src/Type/ResponseStatus.php` SDK; bledy rzucane przez `ErrorException($response->get('response'))` w `PolkurierWebService.php`. Pattern dla wszystkich przyszlych metod polkurier API (`createShipment`, `getLabel`, `getStatus`, `cancelOrder`, etc.) | 2026-05-14 | Active |
@@ -279,8 +280,8 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
| Metric | Target | Current | Status | | Metric | Target | Current | Status |
|--------|--------|---------|--------| |--------|--------|---------|--------|
| Liczba zintegrowanych źródeŠzamówień | ≥3 | 3 zrodla importu (Allegro, shopPRO, Erli); Erli wymaga manualnego smoke po migracji | In progress | | Liczba zintegrowanych źródeŠzamówień | ≥3 | 3 zrodla importu (Allegro, shopPRO, Erli); Erli complete in code, live smoke pending operator | Complete in code |
| Generowanie etykiet | DziaĹa | InPost + Erli przez lokalne providery po mapowaniu; Erli wymaga manualnego smoke po migracji | In progress | | Generowanie etykiet | DziaĹa | InPost + Erli przez lokalne providery po mapowaniu; live smoke pending operator | Complete in code |
## Tech Stack ## Tech Stack
@@ -305,6 +306,6 @@ Quick Reference:
--- ---
*PROJECT.md — Updated when requirements or context change* *PROJECT.md — Updated when requirements or context change*
*Last updated: 2026-05-16 after Phase 131 (Erli Tracking + Automation Hooks) closure; v3.8 milestone in progress* *Last updated: 2026-05-16 after Phase 132 (Erli Hardening, Observability + Docs) closure; v3.8 milestone complete in code*

View File

@@ -6,11 +6,11 @@ orderPRO to narzedzie do wielokanalowego zarzadzania sprzedaza. Projekt przechod
## Current Milestone ## Current Milestone
v3.8 Erli Marketplace Integration — In progress v3.8 Erli Marketplace Integration — Complete in code
Pelna integracja z erli.pl wzorowana na istniejacej integracji Allegro: konfiguracja konta/API, pobieranie zamowien, mapowanie i synchronizacja statusow, generowanie etykiet, tracking oraz wlaczenie Erli w istniejace przeplywy automatyzacji, statystyk i obslugi zamowien. Pelna integracja z erli.pl wzorowana na istniejacej integracji Allegro: konfiguracja konta/API, pobieranie zamowien, mapowanie i synchronizacja statusow, generowanie etykiet, tracking oraz wlaczenie Erli w istniejace przeplywy automatyzacji, statystyk i obslugi zamowien.
Progress: 5 of 6 phases complete (83%). Progress: 6 of 6 phases complete (100%).
| Phase | Name | Plans | Status | | Phase | Name | Plans | Status |
|-------|------|-------|--------| |-------|------|-------|--------|
@@ -19,7 +19,7 @@ Progress: 5 of 6 phases complete (83%).
| 129 | Erli Status Mapping + Sync | 1/1 | Complete (2026-05-16; migration/manual Erli status smoke pending operator) | | 129 | Erli Status Mapping + Sync | 1/1 | Complete (2026-05-16; migration/manual Erli status smoke pending operator) |
| 130 | Erli Shipments + Labels | 1/1 | Complete (2026-05-16; migration/manual Erli shipping smoke pending operator) | | 130 | Erli Shipments + Labels | 1/1 | Complete (2026-05-16; migration/manual Erli shipping smoke pending operator) |
| 131 | Erli Tracking + Automation Hooks | 1/1 | Complete (2026-05-16; manual Erli tracking/automation smoke pending operator) | | 131 | Erli Tracking + Automation Hooks | 1/1 | Complete (2026-05-16; manual Erli tracking/automation smoke pending operator) |
| 132 | Erli Hardening, Observability + Docs | TBD | Not started | | 132 | Erli Hardening, Observability + Docs | 1/1 | Complete (2026-05-16; PHPUnit/Sonar env gaps documented) |
### Phase 127: Erli Integration Foundation ### Phase 127: Erli Integration Foundation
@@ -49,7 +49,7 @@ Plans: 131-01 (complete)
### Phase 132: Erli Hardening, Observability + Docs ### Phase 132: Erli Hardening, Observability + Docs
Focus: Testy jednostkowe mapperow/klientow, logi integracji i bledow API, retry/idempotencja, manual smoke checklist na zywej konfiguracji oraz aktualizacja `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md` i `DOCS/TECH_CHANGELOG.md`. Focus: Testy jednostkowe mapperow/klientow, logi integracji i bledow API, retry/idempotencja, manual smoke checklist na zywej konfiguracji oraz aktualizacja `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md` i `DOCS/TECH_CHANGELOG.md`.
Plans: TBD (defined during $paul-plan) Plans: 132-01 (complete)
## Previous Milestone (transition pending) ## Previous Milestone (transition pending)
@@ -561,4 +561,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
--- ---
*Roadmap created: 2026-03-12* *Roadmap created: 2026-03-12*
*Last updated: 2026-05-16 - Phase 131 complete, ready for Phase 132 planning* *Last updated: 2026-05-16 - Phase 132 complete; v3.8 complete in code*

View File

@@ -5,42 +5,42 @@
See: .paul/PROJECT.md (updated 2026-05-16) See: .paul/PROJECT.md (updated 2026-05-16)
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami. **Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
**Current focus:** v3.8 Erli Marketplace Integration - Phase 132 ready to plan. **Current focus:** v3.8 Erli Marketplace Integration complete in code; next milestone selection pending.
## Current Position ## Current Position
Milestone: v3.8 Erli Marketplace Integration Milestone: v3.8 Erli Marketplace Integration
Phase: 132 of 132 (Erli Hardening, Observability + Docs) - Not started Phase: 132 of 132 (Erli Hardening, Observability + Docs) - Complete
Plan: Not started Plan: 132-01 unified
Status: Ready to plan Status: Loop complete; v3.8 complete in code
Last activity: 2026-05-16 15:36 - Phase 131 complete, transitioned to Phase 132 Last activity: 2026-05-16 15:51 - Unified .paul/phases/132-erli-hardening-observability-docs/132-01-PLAN.md
Progress: Progress:
- Milestone v3.8: [########--] 83% (Phases 127-131 complete; Phase 132 next) - Milestone v3.8: [##########] 100% (Phases 127-132 complete in code)
- Phase 132: [----------] 0% (not planned) - Phase 132: [##########] 100% (complete)
## Loop Position ## Loop Position
Current loop state: Current loop state:
``` ```
PLAN -> APPLY -> UNIFY PLAN -> APPLY -> UNIFY
done done done [Loop complete, ready for next PLAN] done done done [Loop complete - ready for next milestone]
``` ```
## Session Continuity ## Session Continuity
Last session: 2026-05-16 15:36 Last session: 2026-05-16 15:51
Stopped at: Phase 131 complete, ready to plan Phase 132 Stopped at: Phase 132 complete; v3.8 complete in code
Next action: $paul-plan for Phase 132 (Erli Hardening, Observability + Docs) Next action: Choose next milestone or run $paul-complete-milestone for v3.8 archival/release closure
Resume file: .paul/ROADMAP.md Resume file: .paul/phases/132-erli-hardening-observability-docs/132-01-SUMMARY.md
## Pending parallel work ## Pending parallel work
- None — Phase 118, 121, 122 wszystkie zacommitowane (8f14851, 360eef1). - None — Phase 118, 121, 122 wszystkie zacommitowane (8f14851, 360eef1).
## Git State ## Git State
Last phase commit: feat(131): erli tracking automation hooks (created during UNIFY) Last phase commit: feat(132): erli hardening observability docs
Previous: 13f570e feat(130): erli shipments and labels Previous: feat(131): erli tracking automation hooks
Branch: main Branch: main
### Skill Audit (Phase 129) ### Skill Audit (Phase 129)
@@ -61,6 +61,12 @@ Branch: main
|----------|---------|-------| |----------|---------|-------|
| `sonar-scanner` | gap documented | Attempted after APPLY; CLI is not available in PATH. | | `sonar-scanner` | gap documented | Attempted after APPLY; CLI is not available in PATH. |
### Skill Audit (Phase 132)
| Expected | Invoked | Notes |
|----------|---------|-------|
| `sonar-scanner` | gap documented | Attempted after APPLY; CLI is not available in PATH. |
## Pending Actions ## Pending Actions
- Manualne testy AC-1..AC-7 dla Phase 112 na zywej bazie (XAMPP online). - Manualne testy AC-1..AC-7 dla Phase 112 na zywej bazie (XAMPP online).
@@ -94,6 +100,8 @@ Branch: main
- Phase 131 verification gap: `vendor/bin/phpunit` nie istnieje w checkoutcie, wiec testy `tests/Unit/ErliExternalShipmentServiceTest.php` i `tests/Unit/AutomationServiceTest.php` nie zostaly uruchomione przez PHPUnit; wykonano `php -l` i `git diff --check`. - Phase 131 verification gap: `vendor/bin/phpunit` nie istnieje w checkoutcie, wiec testy `tests/Unit/ErliExternalShipmentServiceTest.php` i `tests/Unit/AutomationServiceTest.php` nie zostaly uruchomione przez PHPUnit; wykonano `php -l` i `git diff --check`.
- Phase 131 skill gap: `sonar-scanner` nie jest dostepny w PATH, wiec skan SonarQube nie zostal uruchomiony. - Phase 131 skill gap: `sonar-scanner` nie jest dostepny w PATH, wiec skan SonarQube nie zostal uruchomiony.
- Phase 131 follow-up: manualny smoke po migracjach/konfiguracji Erli — utworz paczke dla zamowienia Erli z lokalnym providerem, potwierdz `shipment.created`, uruchom `shipment_tracking_sync`, sprawdz `shipment.status_changed` i retry `POST /shipping/external` tylko po pojawieniu sie tracking number. - Phase 131 follow-up: manualny smoke po migracjach/konfiguracji Erli — utworz paczke dla zamowienia Erli z lokalnym providerem, potwierdz `shipment.created`, uruchom `shipment_tracking_sync`, sprawdz `shipment.status_changed` i retry `POST /shipping/external` tylko po pojawieniu sie tracking number.
- Phase 132 verification gap: `vendor/bin/phpunit` nie istnieje w checkoutcie, a globalny XAMPP PHPUnit jest niekompatybilny z PHP (`each()` removed), wiec testy `ErliOrdersSyncServiceTest`, `ErliStatusSyncServiceTest` i `ErliOrderMapperTest` nie zostaly uruchomione przez PHPUnit; wykonano `php -l` i `git diff --check`.
- Phase 132 skill gap: `sonar-scanner` nie jest dostepny w PATH, wiec skan SonarQube nie zostal uruchomiony.
- Phase 127 follow-up: zaplanowac kolejna faze polkurier — `PolkurierShipmentService` (CreateOrder + GetLabel + OrderValuationV2 + AvailableCarriers mapping + UI mapowan metod dostawy + presety przesylek) — fundament + zweryfikowany kontrakt API gotowy. - Phase 127 follow-up: zaplanowac kolejna faze polkurier — `PolkurierShipmentService` (CreateOrder + GetLabel + OrderValuationV2 + AvailableCarriers mapping + UI mapowan metod dostawy + presety przesylek) — fundament + zweryfikowany kontrakt API gotowy.
- Phase 127 follow-up: drugi krok — `PolkurierTrackingService` + wpisy w `delivery_status_mappings` (provider='polkurier'). - Phase 127 follow-up: drugi krok — `PolkurierTrackingService` + wpisy w `delivery_status_mappings` (provider='polkurier').
- Phase 127 follow-up: po polkurier shipment service rozwazyc fazy paczkomaty (`InpostParcelMachines` / `PocztexPostOffices` / `Kurier48PostOffices` API juz dostepne w SDK polkuriera). - Phase 127 follow-up: po polkurier shipment service rozwazyc fazy paczkomaty (`InpostParcelMachines` / `PocztexPostOffices` / `Kurier48PostOffices` API juz dostepne w SDK polkuriera).
@@ -115,4 +123,4 @@ Branch: main
## Skill Requirements ## Skill Requirements
- `sonar-scanner` required after APPLY; Phase 116, Phase 117, Phase 121, Phase 122, Phase 128, Phase 129, Phase 130 and Phase 131 gaps documented because CLI was not available in PATH. - `sonar-scanner` required after APPLY; Phase 116, Phase 117, Phase 121, Phase 122, Phase 128, Phase 129, Phase 130, Phase 131 and Phase 132 gaps documented because CLI was not available in PATH.

View File

@@ -17,6 +17,11 @@
- Uzupelniono kontekst `shipment.created` i `shipment.status_changed` o wspolne pola `source`, `tracking_number`, `package_id`, `provider` i statusy dostawy. - Uzupelniono kontekst `shipment.created` i `shipment.status_changed` o wspolne pola `source`, `tracking_number`, `package_id`, `provider` i statusy dostawy.
- Dodano testy dla skip/idempotencji/bledu API w `ErliExternalShipmentServiceTest` oraz test wspolnego kontekstu automatyzacji Erli. - Dodano testy dla skip/idempotencji/bledu API w `ErliExternalShipmentServiceTest` oraz test wspolnego kontekstu automatyzacji Erli.
- Udokumentowano gapy srodowiskowe Phase 131: brak `vendor/bin/phpunit`, brak `sonar-scanner` w PATH, manualny smoke Erli tracking/automation pending. - Udokumentowano gapy srodowiskowe Phase 131: brak `vendor/bin/phpunit`, brak `sonar-scanner` w PATH, manualny smoke Erli tracking/automation pending.
- [Phase 132, Plan 01] Domknieto hardening Erli bez nowej migracji: import/status sync uzywa istniejacych punktow obserwowalnosci.
- `ErliOrdersSyncService` zwraca `last_error`, normalizuje bledy per-message do `message_id/type/error` i nie wykonuje ACK po blednym batchu.
- Blad `POST /inbox/mark-read` jest zapisywany przez istniejacy state failure path z czytelnym komunikatem.
- Dodano `ErliOrdersSyncServiceTest` oraz rozszerzono `ErliStatusSyncServiceTest` o diagnostyke nieudanego push statusu.
- Udokumentowano gapy srodowiskowe Phase 132: brak `vendor/bin/phpunit`, globalny XAMPP PHPUnit niekompatybilny z PHP, brak `sonar-scanner` w PATH.
## Zmienione pliki ## Zmienione pliki
@@ -61,3 +66,6 @@
- `.paul/phases/131-erli-tracking-automation-hooks/131-01-SUMMARY.md` - `.paul/phases/131-erli-tracking-automation-hooks/131-01-SUMMARY.md`
- `src/Modules/Cron/ShipmentTrackingHandler.php` - `src/Modules/Cron/ShipmentTrackingHandler.php`
- `tests/Unit/AutomationServiceTest.php` - `tests/Unit/AutomationServiceTest.php`
- `.paul/phases/132-erli-hardening-observability-docs/132-01-PLAN.md`
- `.paul/phases/132-erli-hardening-observability-docs/132-01-SUMMARY.md`
- `tests/Unit/ErliOrdersSyncServiceTest.php`

View File

@@ -0,0 +1,191 @@
---
phase: 132-erli-hardening-observability-docs
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Modules/Settings/ErliOrdersSyncService.php
- src/Modules/Settings/ErliStatusSyncService.php
- src/Modules/Settings/ErliApiClient.php
- tests/Unit/ErliOrdersSyncServiceTest.php
- tests/Unit/ErliStatusSyncServiceTest.php
- tests/Unit/ErliOrderMapperTest.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
delegation: auto
---
<objective>
## Goal
Utwadzic import i synchronizacje statusow Erli bez dodawania nowych tabel: ujednolicic obsluge bledow, wynikow i istniejacych pol obserwowalnosci dla `/inbox`, ACK i push statusow.
## Purpose
Phase 127-131 dostarczyly pelny przeplyw Erli. Ostatni krok v3.8 ma zmniejszyc ryzyko utraty zdarzen, cichych bledow i nieczytelnych wynikow crona przed zamknieciem milestone.
## Output
Kod import/status sync Erli bedzie mial spojne wyniki i diagnostyke w istniejacych mechanizmach (`integration_order_sync_state.last_error`, wyniki cron, `order_activity_log` tam gdzie juz istnieje). Powstana lub zostana rozszerzone testy jednostkowe oraz sekcje dokumentacji w istniejacych plikach DOCS.
</objective>
<context>
<clarifications>
- **Logi Erli** - Czy dodac nowa trwala tabele logow integracji Erli/API, czy ujednolicic istniejace miejsca?
-> Odpowiedz: Istniejaca, do ujednolicenia.
- **Priorytet** - Czy retry/idempotencja ma dotyczyc najpierw import/status sync, czy wysylek?
-> Odpowiedz: To pierwsze.
- **Smoke docs** - Czy checklist smoke ma byc osobnym dokumentem, czy sekcja w istniejacych DOCS?
-> Odpowiedz: sekcja.
- **Testy** - Czy plan obejmuje tylko testy jednostkowe, czy tez manualny checkpoint live?
-> Odpowiedz: Tylko jednostkowe.
</clarifications>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/codebase/architecture.md
@.paul/codebase/db_schema.md
@.paul/phases/131-erli-tracking-automation-hooks/131-01-SUMMARY.md
## Source Files
@src/Modules/Settings/ErliApiClient.php
@src/Modules/Settings/ErliOrdersSyncService.php
@src/Modules/Settings/ErliStatusSyncService.php
@src/Modules/Settings/ErliOrderSyncStateRepository.php
@tests/Unit/ErliOrderMapperTest.php
@tests/Unit/ErliStatusSyncServiceTest.php
@tests/Unit/ErliExternalShipmentServiceTest.php
@DOCS/DB_SCHEMA.md
@DOCS/ARCHITECTURE.md
@DOCS/TECH_CHANGELOG.md
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| sonar-scanner | required | Po APPLY, przed UNIFY | o |
**BLOCKING:** `sonar-scanner` jest wymagany przez `.paul/SPECIAL-FLOWS.md`. Jezeli CLI nadal nie jest dostepny w PATH, udokumentuj gap w SUMMARY/STATE tak jak w Phases 129-131.
</skills>
<acceptance_criteria>
## AC-1: Spojne wyniki importu Erli
```gherkin
Given cron albo reczny import Erli pobiera wiadomosci z `/inbox`
When czesc wiadomosci zostanie pominieta, przetworzona albo zakonczy sie bledem
Then wynik sync zawiera spojne liczniki, maksymalnie 20 diagnostycznych bledow z message_id/type/error i zapisuje zwiezly `last_error` tylko przy realnym bledzie
```
## AC-2: ACK pozostaje bezpieczny i idempotentny
```gherkin
Given batch inbox Erli zawiera poprawne oraz bledne zdarzenia
When przetwarzanie ma co najmniej jeden blad
Then `/inbox/mark-read` nie jest wywolywany, cursor sukcesu nie przesuwa sie, a bledy sa widoczne w wyniku i `last_error`
```
## AC-3: Push statusow Erli nie gubi kursora
```gherkin
Given kierunek synchronizacji to `orderpro_to_erli`
When wysylka statusu dla pozniejszego zamowienia nie powiedzie sie
Then `last_status_pushed_at` przesuwa sie najwyzej do ostatniej udanej zmiany przed bledem, a wynik zawiera source_order_id, status orderPRO, status Erli i komunikat bledu
```
## AC-4: Dokumentacja zamyka Phase 132 bez nowego schematu
```gherkin
Given Phase 132 nie dodaje nowych tabel ani kolumn
When plan zostanie wykonany
Then `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md` i `DOCS/TECH_CHANGELOG.md` opisuja brak migracji, istniejace punkty obserwowalnosci oraz jednostkowy smoke checklist Erli jako sekcje
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Ujednolicic diagnostyke importu i ACK Erli</name>
<files>src/Modules/Settings/ErliOrdersSyncService.php, src/Modules/Settings/ErliApiClient.php</files>
<action>
Doprecyzuj wynik `ErliOrdersSyncService::sync()` bez zmiany kontraktu publicznego:
- zachowaj obecne liczniki `processed/imported_created/imported_updated/failed/skipped/acknowledged`;
- dopilnuj, aby bledy per-message zawsze mialy `message_id`, `type` i `error`;
- przy blednym batchu nie wolaj ACK i zapisz czytelny, krotki `last_error`;
- przy bledzie ACK zapisz czytelny `last_error` i rzuc istniejacy `IntegrationConfigException`;
- nie dodawaj nowej tabeli logow ani nowego runtime env.
Jezeli `ErliApiClient` wymaga drobnej normalizacji komunikatow API, trzymaj ja lokalnie w kliencie i zachowaj obecne nazwy metod.
</action>
<verify>php -l src/Modules/Settings/ErliOrdersSyncService.php; php -l src/Modules/Settings/ErliApiClient.php</verify>
<done>AC-1 i AC-2 satisfied: wyniki oraz bledy importu/ACK sa testowalne i nie potwierdzaja blednego batcha.</done>
</task>
<task type="auto">
<name>Task 2: Utrwalic unit tests dla import/status sync</name>
<files>tests/Unit/ErliOrdersSyncServiceTest.php, tests/Unit/ErliStatusSyncServiceTest.php, tests/Unit/ErliOrderMapperTest.php</files>
<action>
Dodaj testy jednostkowe bez zewnetrznego API:
- `ErliOrdersSyncServiceTest`: happy path z ACK po zero-failure batchu, brak ACK przy bledzie pojedynczej wiadomosci, blad ACK zapisuje failed state;
- `ErliStatusSyncServiceTest`: zachowaj obecne testy i dodaj przypadek, ze cursor push nie przeskakuje za nieudany status;
- `ErliOrderMapperTest`: uzupelnij tylko jezeli potrzebny jest edge case statusu/payloadu odkryty podczas tasku 1.
Uzywaj mockow PHPUnit i istniejacych wzorcow z testow Erli. Nie dodawaj live API, env sekretow ani zaleznosci sieciowych.
</action>
<verify>php -l tests/Unit/ErliOrdersSyncServiceTest.php; php -l tests/Unit/ErliStatusSyncServiceTest.php; php -l tests/Unit/ErliOrderMapperTest.php; vendor/bin/phpunit tests/Unit/ErliOrdersSyncServiceTest.php tests/Unit/ErliStatusSyncServiceTest.php tests/Unit/ErliOrderMapperTest.php</verify>
<done>AC-1, AC-2 i AC-3 satisfied: najwazniejsze sciezki hardeningu sa pokryte unit tests albo gap PHPUnit jest jawnie udokumentowany, jesli binary nadal nie istnieje.</done>
</task>
<task type="auto">
<name>Task 3: Udokumentowac observability i smoke checklist Erli</name>
<files>DOCS/DB_SCHEMA.md, DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
Zaktualizuj dokumentacje techniczna:
- `DOCS/DB_SCHEMA.md`: dopisz przy Erli/integration sync state, ze Phase 132 nie dodaje migracji i uzywa istniejacych pol obserwowalnosci;
- `DOCS/ARCHITECTURE.md`: dodaj sekcje Phase 132 opisujaca import/status hardening, bezpieczny ACK, push cursor i gdzie operator widzi bledy;
- `DOCS/TECH_CHANGELOG.md`: dodaj wpis chronologiczny z "co" i "dlaczego";
- dodaj sekcje jednostkowej smoke checklist w istniejacej dokumentacji, bez tworzenia osobnego pliku.
</action>
<verify>git diff --check -- DOCS/DB_SCHEMA.md DOCS/ARCHITECTURE.md DOCS/TECH_CHANGELOG.md</verify>
<done>AC-4 satisfied: dokumentacja opisuje Phase 132 i potwierdza brak zmian schematu.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Nie dodawaj nowych tabel logow ani migracji dla Phase 132.
- Nie podpinaj `DB_HOST_REMOTE` do runtime aplikacji.
- Nie zmieniaj kontraktu runtime Erli credentials ani `.env.example`, chyba ze kod naprawiany wprost wymaga nowej zmiennej; obecny plan tego nie zaklada.
- Nie zmieniaj flow przesylek Erli (`ErliExternalShipmentService`, `ShipmentTrackingHandler`) poza testami pomocniczymi, bo priorytetem tej fazy jest import/status sync.
- Nie dodawaj native `alert()` / `confirm()` ani inline CSS w widokach.
## SCOPE LIMITS
- Brak nowych widokow UI.
- Brak live smoke i brak wywolan do prawdziwego API Erli w testach.
- Brak nowego systemu retry jobow; hardening ma dzialac w istniejacych cronach i state repository.
- Brak refaktoru ogolnej architektury cronow poza minimalnymi zmianami wymaganymi przez Erli.
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `php -l` dla wszystkich zmienionych plikow PHP.
- [ ] Unit tests Erli uruchomione przez `vendor/bin/phpunit` albo gap jawnie opisany, jezeli binary nadal nie istnieje.
- [ ] `git diff --check -- . ':!.vscode/ftp-kr.sync.cache.json'`.
- [ ] `sonar-scanner --version` i `sonar-scanner` uruchomiony albo gap udokumentowany zgodnie z `.paul/SPECIAL-FLOWS.md`.
- [ ] All acceptance criteria met.
</verification>
<success_criteria>
- Import Erli i status sync maja spojna diagnostyke w istniejacych mechanizmach.
- Bledny batch inbox nie wykonuje ACK.
- Push statusow nie przesuwa kursora poza ostatnia udana zmiane.
- Unit tests pokrywaja najwazniejsze sciezki bez API live.
- Dokumentacja DOCS zostala zaktualizowana bez nowej migracji.
</success_criteria>
<output>
After completion, create `.paul/phases/132-erli-hardening-observability-docs/132-01-SUMMARY.md`.
</output>

View File

@@ -0,0 +1,93 @@
---
phase: 132-erli-hardening-observability-docs
plan: 01
subsystem: erli, integrations, cron, testing, docs
tags: [erli, inbox, ack, status-sync, observability, unit-tests]
requires:
- phase: 128-erli-orders-import
provides: Erli inbox import and ACK flow
- phase: 129-erli-status-mapping-sync
provides: Erli pull/push status synchronization
- phase: 131-erli-tracking-automation-hooks
provides: Completed Erli integration flow before hardening
provides:
- Consistent Erli import diagnostics
- Unit-test coverage for ACK safety and push error payloads
- Phase 132 no-migration observability documentation
affects: [erli_orders_import, erli_status_sync, integration_order_sync_state]
tech-stack:
added: []
patterns: [existing-state observability, zero-failure ACK, push cursor guard]
key-files:
created:
- tests/Unit/ErliOrdersSyncServiceTest.php
modified:
- src/Modules/Settings/ErliOrdersSyncService.php
- tests/Unit/ErliStatusSyncServiceTest.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
key-decisions:
- "No new Erli log table; reuse existing sync state, cron result and activity log surfaces."
- "Phase 132 focuses on import/status sync, not shipment retry."
- "Verification scope is unit tests only; live API smoke remains outside this plan."
duration: 10min
started: 2026-05-16T15:41:00+02:00
completed: 2026-05-16T15:47:00+02:00
---
# Phase 132 Plan 01 Summary - Erli Hardening, Observability + Docs
## Status
APPLY complete on 2026-05-16 15:47. Ready for UNIFY.
## Implemented
- Added `last_error` to `ErliOrdersSyncService::sync()` results for enabled and disabled paths.
- Normalized Erli per-message errors to `message_id`, `type`, and `error`.
- Changed failed inbox batches to write a concise diagnostic message to `integration_order_sync_state.last_error` and skip ACK.
- Wrapped ACK failures as `Nie udalo sie potwierdzic inbox Erli: ...`, allowing the existing run-failure state path to persist the issue.
- Added `tests/Unit/ErliOrdersSyncServiceTest.php` for successful ACK, no ACK on failed message, and failed ACK state behavior.
- Extended `ErliStatusSyncServiceTest` to assert structured push-error diagnostics.
- Updated `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md`, and `DOCS/TECH_CHANGELOG.md` with Phase 132 observability, no-migration notes, and unit smoke checklist.
## Acceptance Criteria Results
| AC | Status | Notes |
|----|--------|-------|
| AC-1: Spojne wyniki importu Erli | Pass | `sync()` returns `last_error` and normalized per-message diagnostics capped by existing error list behavior. |
| AC-2: ACK pozostaje bezpieczny i idempotentny | Pass | Failed message batches return before ACK; ACK failures go through the existing failed-run state path. |
| AC-3: Push statusow Erli nie gubi kursora | Pass | Existing cursor guard preserved and test now asserts structured failed-push diagnostics. |
| AC-4: Dokumentacja zamyka Phase 132 bez nowego schematu | Pass | DOCS updated; no migration added. |
## Verification
- PASS: `php -l src\Modules\Settings\ErliOrdersSyncService.php`
- PASS: `php -l src\Modules\Settings\ErliApiClient.php`
- PASS: `php -l tests\Unit\ErliOrdersSyncServiceTest.php`
- PASS: `php -l tests\Unit\ErliStatusSyncServiceTest.php`
- PASS: `php -l tests\Unit\ErliOrderMapperTest.php`
- PASS: `git diff --check -- . ':!.vscode/ftp-kr.sync.cache.json'` (LF/CRLF warnings only)
- BLOCKED: `vendor/bin/phpunit` is missing.
- BLOCKED: global `C:\xampp\php\phpunit.bat` is incompatible with current PHP (`each()` removed).
- BLOCKED: `sonar-scanner` is not available in PATH.
## Deviations / Gaps
- Unit tests were added and linted, but not executed by PHPUnit because the checkout lacks `vendor/bin/phpunit` and XAMPP's legacy PHPUnit crashes before loading tests.
- SonarQube scan remains unavailable in PATH, consistent with Phases 129-131.
- `.vscode/ftp-kr.sync.cache.json` was already dirty and was not touched by this phase.
## Files Changed
- `src/Modules/Settings/ErliOrdersSyncService.php`
- `tests/Unit/ErliOrdersSyncServiceTest.php`
- `tests/Unit/ErliStatusSyncServiceTest.php`
- `DOCS/DB_SCHEMA.md`
- `DOCS/ARCHITECTURE.md`
- `DOCS/TECH_CHANGELOG.md`
- `.paul/phases/132-erli-hardening-observability-docs/132-01-PLAN.md`
- `.paul/phases/132-erli-hardening-observability-docs/132-01-SUMMARY.md`
- `.paul/STATE.md`
- `.paul/ROADMAP.md`

View File

@@ -131,6 +131,7 @@ Phase 130 adds an Erli-specific post-label step: for `orders.source='erli'`, `Sh
6. **Delivery mapping and labels** - Phase 130 adds `ErliDeliveryMappingController` and a Delivery tab. It maps imported Erli delivery method labels to local shipment providers (`inpost`/`apaczka`) and stores Erli `source_vendor_code` for external parcel registration. Label files are still produced by local providers, not by Erli carrier-contract parcels. 6. **Delivery mapping and labels** - Phase 130 adds `ErliDeliveryMappingController` and a Delivery tab. It maps imported Erli delivery method labels to local shipment providers (`inpost`/`apaczka`) and stores Erli `source_vendor_code` for external parcel registration. Label files are still produced by local providers, not by Erli carrier-contract parcels.
7. **External shipment sync** - Phase 130 extends `ErliApiClient` with shipping dictionary calls and `createExternalParcel()` (`POST /shipping/external`). `ErliExternalShipmentService` registers a local package in Erli only after `shipment_packages.tracking_number` exists; failures are activity-log warnings and do not block local labels. Phase 131 wires the same service into `shipment_tracking_sync` as a retry path when tracking appears after the initial create/label flow. 7. **External shipment sync** - Phase 130 extends `ErliApiClient` with shipping dictionary calls and `createExternalParcel()` (`POST /shipping/external`). `ErliExternalShipmentService` registers a local package in Erli only after `shipment_packages.tracking_number` exists; failures are activity-log warnings and do not block local labels. Phase 131 wires the same service into `shipment_tracking_sync` as a retry path when tracking appears after the initial create/label flow.
8. **Tracking + automation hooks** - Phase 131 deliberately follows the working Allegro model: no separate `ErliTrackingService` and no Erli-only delivery-status UI. Local provider tracking remains source of truth, `ShipmentTrackingHandler` emits shared `shipment.status_changed` context (`source`, `package_id`, `provider`, `tracking_number`, status fields), `ShipmentController` emits `shipment.created` with the same generic context, and e-mail/SMS variables continue to use `ShipmentPackageRepository::findLatestByOrderId()` plus `DeliveryStatus::trackingUrl()`. 8. **Tracking + automation hooks** - Phase 131 deliberately follows the working Allegro model: no separate `ErliTrackingService` and no Erli-only delivery-status UI. Local provider tracking remains source of truth, `ShipmentTrackingHandler` emits shared `shipment.status_changed` context (`source`, `package_id`, `provider`, `tracking_number`, status fields), `ShipmentController` emits `shipment.created` with the same generic context, and e-mail/SMS variables continue to use `ShipmentPackageRepository::findLatestByOrderId()` plus `DeliveryStatus::trackingUrl()`.
9. **Hardening + observability** - Phase 132 keeps the existing schema. `ErliOrdersSyncService` returns consistent counters and per-message diagnostics, writes concise failures to `integration_order_sync_state.last_error`, and never acknowledges `/inbox/mark-read` when any message in the batch failed. `ErliStatusSyncService` keeps the push cursor at the latest successfully pushed manual status change and returns structured error details for failed pushes.
## Dependency Injection ## Dependency Injection
@@ -211,10 +212,11 @@ tests/
- Wysyla `POST /shipping/external` z `orderId`, `vendor`, `status='sent'`, `trackingNumber`; sukces zapisuje w `shipment_packages.payload_json.erli_external_parcel`, blad trafia do activity log jako niekrytyczny. - Wysyla `POST /shipping/external` z `orderId`, `vendor`, `status='sent'`, `trackingNumber`; sukces zapisuje w `shipment_packages.payload_json.erli_external_parcel`, blad trafia do activity log jako niekrytyczny.
### ErliOrdersSyncService / ErliOrderMapper (`src/Modules/Settings/`) ### ErliOrdersSyncService / ErliOrderMapper (`src/Modules/Settings/`)
- `ErliOrdersSyncService::sync()` jest wspolnym entrypointem dla crona i importu recznego. Zwraca liczniki `processed`, `imported_created`, `imported_updated`, `failed`, `skipped`, `acknowledged`. - `ErliOrdersSyncService::sync()` jest wspolnym entrypointem dla crona i importu recznego. Zwraca liczniki `processed`, `imported_created`, `imported_updated`, `failed`, `skipped`, `acknowledged`, `acknowledged_count`, `latest_message_id`, `last_error` i maksymalnie 20 wpisow `errors`.
- Obsluguje tylko zdarzenia order inbox (`orderCreated`, `orderStatusChanged`, `orderSellerStatusChanged`); wiadomosci produktowe sa pomijane do przyszlych faz. - Obsluguje tylko zdarzenia order inbox (`orderCreated`, `orderStatusChanged`, `orderSellerStatusChanged`); wiadomosci produktowe sa pomijane do przyszlych faz.
- `ErliOrderMapper` mapuje statusy przez `ErliPullStatusMappingRepository` gdy istnieje konfiguracja; w przeciwnym razie zachowuje fallbacki `pending -> nieoplacone`, `purchased -> nowe`, `cancelled/returned -> anulowane`. - `ErliOrderMapper` mapuje statusy przez `ErliPullStatusMappingRepository` gdy istnieje konfiguracja; w przeciwnym razie zachowuje fallbacki `pending -> nieoplacone`, `purchased -> nowe`, `cancelled/returned -> anulowane`.
- `ErliOrdersSyncService` odkrywa surowe statusy Erli z inboxa i dopisuje je do `erli_order_status_pull_mappings`, zeby operator mogl je zmapowac w UI. - `ErliOrdersSyncService` odkrywa surowe statusy Erli z inboxa i dopisuje je do `erli_order_status_pull_mappings`, zeby operator mogl je zmapowac w UI.
- Phase 132: blad pojedynczej wiadomosci dopisuje diagnostyke `message_id/type/error`, blokuje ACK calego batcha i zapisuje krotki komunikat w `integration_order_sync_state.last_error`. Blad samego ACK rzuca `IntegrationConfigException` i rowniez trafia do `last_error`.
- Nowe zamowienia z invoice/company/tax id ustawiają `orders.invoice_requested=1`; re-import korzysta z istniejacego delta-only kontraktu `OrderImportRepository`. - Nowe zamowienia z invoice/company/tax id ustawiają `orders.invoice_requested=1`; re-import korzysta z istniejacego delta-only kontraktu `OrderImportRepository`.
- Automatyzacje: `order.imported` dla nowych zamowien i `payment.status_changed` przy tranzycji platnosci na re-imporcie. - Automatyzacje: `order.imported` dla nowych zamowien i `payment.status_changed` przy tranzycji platnosci na re-imporcie.
@@ -226,6 +228,7 @@ tests/
- Kierunek `orderpro_to_erli` wybiera tylko zamowienia `source='erli'` z reczna zmiana statusu (`order_status_history.change_source='manual'`) po `integration_order_sync_state.last_status_pushed_at`. - Kierunek `orderpro_to_erli` wybiera tylko zamowienia `source='erli'` z reczna zmiana statusu (`order_status_history.change_source='manual'`) po `integration_order_sync_state.last_status_pushed_at`.
- Push korzysta z `erli_order_status_mappings` i `ErliApiClient::updateOrderStatus()`. Brak mapowania powoduje `skipped`; blad API powoduje `failed` i nie przesuwa kursora poza ostatni udany timestamp. - Push korzysta z `erli_order_status_mappings` i `ErliApiClient::updateOrderStatus()`. Brak mapowania powoduje `skipped`; blad API powoduje `failed` i nie przesuwa kursora poza ostatni udany timestamp.
- `erli_status_sync` jest seedowany jako disabled; zapis ustawien Erli aktualizuje interwal, kierunek i wlaczenie harmonogramu zgodnie z aktywnoscia integracji. - `erli_status_sync` jest seedowany jako disabled; zapis ustawien Erli aktualizuje interwal, kierunek i wlaczenie harmonogramu zgodnie z aktywnoscia integracji.
- Unit smoke checklist Phase 132: testy powinny potwierdzic happy path importu z ACK, brak ACK przy bledzie mapowania jednej wiadomosci, zapis bledu ACK w state oraz brak przesuniecia `last_status_pushed_at` poza ostatni udany push.
### IntegrationsHubController ### IntegrationsHubController
- Dodaje wiersz Erli do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu. - Dodaje wiersz Erli do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu.

View File

@@ -375,10 +375,12 @@ UNIQUE: `(integration_id, shoppro_status_code)`
| `last_run_at` | DATETIME | YES | | | `last_run_at` | DATETIME | YES | |
| `last_success_at` | DATETIME | YES | | | `last_success_at` | DATETIME | YES | |
| `last_status_pushed_at` | DATETIME | YES | Erli status push cursor for manual local status changes | | `last_status_pushed_at` | DATETIME | YES | Erli status push cursor for manual local status changes |
| `last_error` | VARCHAR(500) | YES | | | `last_error` | VARCHAR(500) | YES | Phase 132 stores concise Erli import/ACK diagnostics here; no separate Erli log table |
| `created_at` | DATETIME | NO | | | `created_at` | DATETIME | NO | |
| `updated_at` | DATETIME | NO | | | `updated_at` | DATETIME | NO | |
Phase 132 does not add migrations. Erli hardening reuses this table for import run timestamps, the acknowledged inbox message cursor, manual status-push cursor, and last error text.
**integration_order_status_sync_state** — Track status sync progress per integration and direction **integration_order_status_sync_state** — Track status sync progress per integration and direction
--- ---
@@ -675,7 +677,7 @@ UNIQUE: `(integration_id)` - one global SMSPLANET settings row.
| `created_at` | DATETIME | NO | DEFAULT CURRENT_TIMESTAMP | | `created_at` | DATETIME | NO | DEFAULT CURRENT_TIMESTAMP |
| `updated_at` | DATETIME | NO | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | | `updated_at` | DATETIME | NO | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP |
UNIQUE: `(integration_id)` - one global Erli settings row. Base integration uses `base_url='https://erli.pl/svc/shop-api'`. Phase 127 only stores configuration and test results; order import, status sync, shipments and tracking are deferred to later v3.8 phases. UNIQUE: `(integration_id)` - one global Erli settings row. Base integration uses `base_url='https://erli.pl/svc/shop-api'`. Phase 127 stores configuration and test results; Phases 128-131 add order import, status sync, shipments and tracking. Phase 132 adds hardening/observability in existing sync-state and cron/activity-log fields without schema changes.
--- ---

View File

@@ -1,5 +1,21 @@
# Technical Changelog # Technical Changelog
## 2026-05-16 - Phase 132 Plan 01: Erli Hardening, Observability + Docs
**Co zrobiono:**
- `ErliOrdersSyncService::sync()` zwraca teraz jawne `last_error` oraz normalizuje wpisy `errors` do postaci `message_id/type/error`.
- Blad pojedynczej wiadomosci inbox tworzy zwiezly komunikat w `integration_order_sync_state.last_error` i blokuje ACK calego batcha.
- Blad `POST /inbox/mark-read` jest opakowany w czytelny komunikat `Nie udalo sie potwierdzic inbox Erli: ...` i zapisywany jako failure run przez istniejacy state repository.
- Dodano `tests/Unit/ErliOrdersSyncServiceTest.php` z testami happy path ACK, braku ACK przy bledzie wiadomosci oraz failure state przy bledzie ACK.
- Wzmocniono `ErliStatusSyncServiceTest` o jawne asercje diagnostyki nieudanego push statusu: source order, status orderPRO, status Erli i komunikat bledu.
- Uaktualniono dokumentacje Erli o istniejace punkty obserwowalnosci i jednostkowy smoke checklist.
**Dlaczego:**
- Phase 132 zamyka v3.8 przez utwardzenie importu/status sync bez rozbudowy schematu. Operator ma widziec, dlaczego batch sie nie potwierdzil, a system nie moze zgubic zdarzen Erli przez ACK po czesciowej awarii.
**BREAKING / migracja:**
- Brak migracji i brak breaking changes. Zmiana uzywa istniejacych pol `integration_order_sync_state.last_error`, wynikow crona oraz istniejacych testow jednostkowych.
## 2026-05-16 - Phase 131 Plan 01: Erli Tracking + Automation Hooks ## 2026-05-16 - Phase 131 Plan 01: Erli Tracking + Automation Hooks
**Co zrobiono:** **Co zrobiono:**

View File

@@ -56,6 +56,7 @@ final class ErliOrdersSyncService
'acknowledged' => false, 'acknowledged' => false,
'acknowledged_count' => 0, 'acknowledged_count' => 0,
'latest_message_id' => null, 'latest_message_id' => null,
'last_error' => null,
'errors' => [], 'errors' => [],
]; ];
@@ -119,10 +120,11 @@ final class ErliOrdersSyncService
} }
if ((int) $result['failed'] > 0) { if ((int) $result['failed'] > 0) {
$result['last_error'] = $this->buildBatchFailureMessage($result);
$this->syncStateRepository->markRunFailed( $this->syncStateRepository->markRunFailed(
$integrationId, $integrationId,
new DateTimeImmutable('now'), new DateTimeImmutable('now'),
'Erli import zakonczony z bledami: ' . (string) $result['failed'] (string) $result['last_error']
); );
return $result; return $result;
} }
@@ -130,7 +132,9 @@ final class ErliOrdersSyncService
if ($latestMessageId !== null) { if ($latestMessageId !== null) {
$ack = $this->apiClient->markInboxRead($credentials, $latestMessageId); $ack = $this->apiClient->markInboxRead($credentials, $latestMessageId);
if (($ack['ok'] ?? false) !== true) { if (($ack['ok'] ?? false) !== true) {
throw new IntegrationConfigException('Nie udalo sie potwierdzic inbox Erli: ' . (string) ($ack['message'] ?? '')); $message = $this->buildAckFailureMessage((string) ($ack['message'] ?? ''));
$result['last_error'] = $message;
throw new IntegrationConfigException($message);
} }
$result['acknowledged'] = true; $result['acknowledged'] = true;
$result['acknowledged_count'] = (int) ($ack['acknowledged_count'] ?? 0); $result['acknowledged_count'] = (int) ($ack['acknowledged_count'] ?? 0);
@@ -170,6 +174,7 @@ final class ErliOrdersSyncService
'acknowledged' => false, 'acknowledged' => false,
'acknowledged_count' => 0, 'acknowledged_count' => 0,
'latest_message_id' => null, 'latest_message_id' => null,
'last_error' => null,
'errors' => [], 'errors' => [],
]; ];
} }
@@ -306,8 +311,48 @@ final class ErliOrdersSyncService
{ {
$errors = is_array($result['errors'] ?? null) ? $result['errors'] : []; $errors = is_array($result['errors'] ?? null) ? $result['errors'] : [];
if (count($errors) < 20) { if (count($errors) < 20) {
$errors[] = $error; $errors[] = [
'message_id' => trim((string) ($error['message_id'] ?? '')),
'type' => trim((string) ($error['type'] ?? '')),
'error' => trim((string) ($error['error'] ?? 'Nieznany blad Erli.')),
];
} }
$result['errors'] = $errors; $result['errors'] = $errors;
} }
/**
* @param array<string, mixed> $result
*/
private function buildBatchFailureMessage(array $result): string
{
$failed = (int) ($result['failed'] ?? 0);
$errors = is_array($result['errors'] ?? null) ? $result['errors'] : [];
$firstError = is_array($errors[0] ?? null) ? $errors[0] : [];
$messageId = trim((string) ($firstError['message_id'] ?? ''));
$type = trim((string) ($firstError['type'] ?? ''));
$error = trim((string) ($firstError['error'] ?? ''));
$parts = ['Erli import zakonczony z bledami: ' . $failed];
if ($messageId !== '') {
$parts[] = 'message_id=' . $messageId;
}
if ($type !== '') {
$parts[] = 'type=' . $type;
}
if ($error !== '') {
$parts[] = 'error=' . $error;
}
return mb_substr(implode('; ', $parts), 0, 500);
}
private function buildAckFailureMessage(string $message): string
{
$trimmed = trim($message);
if ($trimmed === '') {
$trimmed = 'Brak szczegolow bledu.';
}
return mb_substr('Nie udalo sie potwierdzic inbox Erli: ' . $trimmed, 0, 500);
}
} }

View File

@@ -0,0 +1,229 @@
<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Core\Exceptions\IntegrationConfigException;
use App\Modules\Orders\OrderImportRepository;
use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\ErliApiClient;
use App\Modules\Settings\ErliIntegrationRepository;
use App\Modules\Settings\ErliOrderMapper;
use App\Modules\Settings\ErliOrdersSyncService;
use App\Modules\Settings\ErliOrderSyncStateRepository;
use DateTimeImmutable;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use RuntimeException;
final class ErliOrdersSyncServiceTest extends TestCase
{
private ErliIntegrationRepository&MockObject $integrationRepository;
private ErliOrderSyncStateRepository&MockObject $syncStateRepository;
private ErliApiClient&MockObject $apiClient;
private OrderImportRepository&MockObject $orderImportRepository;
private OrdersRepository&MockObject $ordersRepository;
private ErliOrderMapper&MockObject $mapper;
protected function setUp(): void
{
$this->integrationRepository = $this->createMock(ErliIntegrationRepository::class);
$this->syncStateRepository = $this->createMock(ErliOrderSyncStateRepository::class);
$this->apiClient = $this->createMock(ErliApiClient::class);
$this->orderImportRepository = $this->createMock(OrderImportRepository::class);
$this->ordersRepository = $this->createMock(OrdersRepository::class);
$this->mapper = $this->createMock(ErliOrderMapper::class);
}
public function testSuccessfulBatchAcknowledgesLatestMessageAndMarksSuccess(): void
{
$this->prepareCredentials();
$this->apiClient
->method('fetchInbox')
->willReturn([
'ok' => true,
'http_code' => 200,
'items' => [
$this->message('m1', '2026-05-16T10:00:00+02:00'),
$this->message('m2', '2026-05-16T10:10:00+02:00'),
],
'message' => 'OK',
]);
$this->mapper
->method('mapInboxMessage')
->willReturn($this->mappedAggregate());
$this->orderImportRepository
->method('upsertOrderAggregate')
->willReturn(['order_id' => 0, 'created' => true, 'payment_transition' => false]);
$this->apiClient
->expects($this->once())
->method('markInboxRead')
->with($this->anything(), 'm2')
->willReturn(['ok' => true, 'http_code' => 200, 'acknowledged_count' => 2, 'message' => 'OK']);
$this->syncStateRepository
->expects($this->once())
->method('markRunSuccess')
->with(12, $this->isInstanceOf(DateTimeImmutable::class), '2026-05-16 10:10:00', 'm2');
$result = $this->createService()->sync();
self::assertSame(2, $result['processed']);
self::assertSame(2, $result['imported_created']);
self::assertSame(0, $result['failed']);
self::assertTrue($result['acknowledged']);
self::assertSame(2, $result['acknowledged_count']);
self::assertNull($result['last_error']);
}
public function testFailedMessageDoesNotAcknowledgeInboxAndPersistsDiagnosticError(): void
{
$this->prepareCredentials();
$this->apiClient
->method('fetchInbox')
->willReturn([
'ok' => true,
'http_code' => 200,
'items' => [
$this->message('m1', '2026-05-16T10:00:00+02:00'),
$this->message('m2', '2026-05-16T10:10:00+02:00'),
],
'message' => 'OK',
]);
$this->mapper
->method('mapInboxMessage')
->willReturnCallback(function (int $integrationId, array $message): array {
if (($message['id'] ?? '') === 'm2') {
throw new RuntimeException('broken mapping');
}
return $this->mappedAggregate();
});
$this->orderImportRepository
->method('upsertOrderAggregate')
->willReturn(['order_id' => 0, 'created' => true, 'payment_transition' => false]);
$this->apiClient
->expects($this->never())
->method('markInboxRead');
$this->syncStateRepository
->expects($this->once())
->method('markRunFailed')
->with(
12,
$this->isInstanceOf(DateTimeImmutable::class),
$this->callback(static function (string $message): bool {
return str_contains($message, 'Erli import zakonczony z bledami: 1')
&& str_contains($message, 'message_id=m2')
&& str_contains($message, 'type=orderCreated')
&& str_contains($message, 'error=broken mapping');
})
);
$result = $this->createService()->sync();
self::assertSame(1, $result['processed']);
self::assertSame(1, $result['failed']);
self::assertFalse($result['acknowledged']);
self::assertSame('m2', $result['errors'][0]['message_id']);
self::assertSame('orderCreated', $result['errors'][0]['type']);
self::assertSame('broken mapping', $result['errors'][0]['error']);
self::assertIsString($result['last_error']);
}
public function testAckFailureIsPersistedAsRunFailure(): void
{
$this->prepareCredentials();
$this->apiClient
->method('fetchInbox')
->willReturn([
'ok' => true,
'http_code' => 200,
'items' => [$this->message('m1', '2026-05-16T10:00:00+02:00')],
'message' => 'OK',
]);
$this->mapper
->method('mapInboxMessage')
->willReturn($this->mappedAggregate());
$this->orderImportRepository
->method('upsertOrderAggregate')
->willReturn(['order_id' => 0, 'created' => true, 'payment_transition' => false]);
$this->apiClient
->expects($this->once())
->method('markInboxRead')
->with($this->anything(), 'm1')
->willReturn(['ok' => false, 'http_code' => 500, 'acknowledged_count' => 0, 'message' => 'ACK down']);
$this->syncStateRepository
->expects($this->once())
->method('markRunFailed')
->with(
12,
$this->isInstanceOf(DateTimeImmutable::class),
'Nie udalo sie potwierdzic inbox Erli: ACK down'
);
$this->expectException(IntegrationConfigException::class);
$this->expectExceptionMessage('Nie udalo sie potwierdzic inbox Erli: ACK down');
$this->createService()->sync();
}
private function prepareCredentials(): void
{
$this->integrationRepository
->method('getCredentials')
->willReturn([
'integration_id' => 12,
'base_url' => 'https://erli.test',
'api_key' => 'token',
'orders_fetch_enabled' => true,
'orders_fetch_start_date' => null,
]);
}
/**
* @return array<string, mixed>
*/
private function message(string $id, string $created): array
{
return [
'id' => $id,
'created' => $created,
'type' => 'orderCreated',
'payload' => ['status' => 'purchased'],
];
}
/**
* @return array<string, mixed>
*/
private function mappedAggregate(): array
{
return [
'message_id' => 'm1',
'invoice_detected' => false,
'order' => [
'integration_id' => 12,
'source_order_id' => 'erli-123',
'source_updated_at' => '2026-05-16 10:00:00',
'payment_status' => 2,
],
'addresses' => [],
'items' => [],
'payments' => [],
'shipments' => [],
'notes' => [],
'status_history' => [],
];
}
private function createService(): ErliOrdersSyncService
{
return new ErliOrdersSyncService(
$this->integrationRepository,
$this->syncStateRepository,
$this->apiClient,
$this->orderImportRepository,
$this->ordersRepository,
$this->mapper
);
}
}

View File

@@ -120,6 +120,10 @@ final class ErliStatusSyncServiceTest extends TestCase
self::assertSame(1, $result['pushed']); self::assertSame(1, $result['pushed']);
self::assertSame(1, $result['failed']); self::assertSame(1, $result['failed']);
self::assertCount(1, $result['errors']); self::assertCount(1, $result['errors']);
self::assertSame('E2', $result['errors'][0]['source_order_id']);
self::assertSame('w_realizacji', $result['errors'][0]['orderpro_status_code']);
self::assertSame('inProgress', $result['errors'][0]['erli_status_code']);
self::assertSame('Erli error', $result['errors'][0]['error']);
} }
public function testPushDirectionReturnsEarlyWithoutCredentials(): void public function testPushDirectionReturnsEarlyWithoutCredentials(): void