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:
@@ -13,8 +13,8 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| Version | 3.8.0-dev |
|
||||
| Status | v3.8 Erli Marketplace Integration in progress — Phase 131 shipped (Erli tracking + automation hooks); Phase 132 next |
|
||||
| Last Updated | 2026-05-16 (Phase 131 closed) |
|
||||
| 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 132 closed) |
|
||||
|
||||
## 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] 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] 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] 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
|
||||
@@ -143,11 +144,10 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
||||
|
||||
### 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)
|
||||
|
||||
- [ ] Erli hardening, observability + docs — Phase 132
|
||||
- [ ] ZarzÄ…dzanie produktami
|
||||
- [ ] ZarzÄ…dzanie stanami magazynowymi
|
||||
- [ ] 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 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 |
|
||||
| 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 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 |
|
||||
@@ -279,8 +280,8 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
|
||||
|
||||
| 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 |
|
||||
| Generowanie etykiet | Działa | InPost + Erli przez lokalne providery po mapowaniu; 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; live smoke pending operator | Complete in code |
|
||||
|
||||
## Tech Stack
|
||||
|
||||
@@ -305,6 +306,6 @@ Quick Reference:
|
||||
|
||||
---
|
||||
*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*
|
||||
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ orderPRO to narzedzie do wielokanalowego zarzadzania sprzedaza. Projekt przechod
|
||||
|
||||
## 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.
|
||||
|
||||
Progress: 5 of 6 phases complete (83%).
|
||||
Progress: 6 of 6 phases complete (100%).
|
||||
|
||||
| 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) |
|
||||
| 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) |
|
||||
| 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
|
||||
|
||||
@@ -49,7 +49,7 @@ Plans: 131-01 (complete)
|
||||
### 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`.
|
||||
Plans: TBD (defined during $paul-plan)
|
||||
Plans: 132-01 (complete)
|
||||
|
||||
## Previous Milestone (transition pending)
|
||||
|
||||
@@ -561,4 +561,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
|
||||
|
||||
---
|
||||
*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*
|
||||
|
||||
@@ -5,42 +5,42 @@
|
||||
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.
|
||||
**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
|
||||
|
||||
Milestone: v3.8 Erli Marketplace Integration
|
||||
Phase: 132 of 132 (Erli Hardening, Observability + Docs) - Not started
|
||||
Plan: Not started
|
||||
Status: Ready to plan
|
||||
Last activity: 2026-05-16 15:36 - Phase 131 complete, transitioned to Phase 132
|
||||
Phase: 132 of 132 (Erli Hardening, Observability + Docs) - Complete
|
||||
Plan: 132-01 unified
|
||||
Status: Loop complete; v3.8 complete in code
|
||||
Last activity: 2026-05-16 15:51 - Unified .paul/phases/132-erli-hardening-observability-docs/132-01-PLAN.md
|
||||
|
||||
Progress:
|
||||
- Milestone v3.8: [########--] 83% (Phases 127-131 complete; Phase 132 next)
|
||||
- Phase 132: [----------] 0% (not planned)
|
||||
- Milestone v3.8: [##########] 100% (Phases 127-132 complete in code)
|
||||
- Phase 132: [##########] 100% (complete)
|
||||
|
||||
## Loop Position
|
||||
|
||||
Current loop state:
|
||||
```
|
||||
PLAN -> APPLY -> UNIFY
|
||||
done done done [Loop complete, ready for next PLAN]
|
||||
done done done [Loop complete - ready for next milestone]
|
||||
```
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-05-16 15:36
|
||||
Stopped at: Phase 131 complete, ready to plan Phase 132
|
||||
Next action: $paul-plan for Phase 132 (Erli Hardening, Observability + Docs)
|
||||
Resume file: .paul/ROADMAP.md
|
||||
Last session: 2026-05-16 15:51
|
||||
Stopped at: Phase 132 complete; v3.8 complete in code
|
||||
Next action: Choose next milestone or run $paul-complete-milestone for v3.8 archival/release closure
|
||||
Resume file: .paul/phases/132-erli-hardening-observability-docs/132-01-SUMMARY.md
|
||||
|
||||
## Pending parallel work
|
||||
- None — Phase 118, 121, 122 wszystkie zacommitowane (8f14851, 360eef1).
|
||||
|
||||
## Git State
|
||||
|
||||
Last phase commit: feat(131): erli tracking automation hooks (created during UNIFY)
|
||||
Previous: 13f570e feat(130): erli shipments and labels
|
||||
Last phase commit: feat(132): erli hardening observability docs
|
||||
Previous: feat(131): erli tracking automation hooks
|
||||
Branch: main
|
||||
|
||||
### Skill Audit (Phase 129)
|
||||
@@ -61,6 +61,12 @@ Branch: main
|
||||
|----------|---------|-------|
|
||||
| `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
|
||||
|
||||
- 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 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 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: 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).
|
||||
@@ -115,4 +123,4 @@ Branch: main
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
@@ -61,3 +66,6 @@
|
||||
- `.paul/phases/131-erli-tracking-automation-hooks/131-01-SUMMARY.md`
|
||||
- `src/Modules/Cron/ShipmentTrackingHandler.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`
|
||||
|
||||
@@ -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>
|
||||
@@ -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`
|
||||
@@ -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.
|
||||
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()`.
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
|
||||
### 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.
|
||||
- `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.
|
||||
- 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`.
|
||||
- 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`.
|
||||
- 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.
|
||||
- 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
|
||||
- Dodaje wiersz Erli do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu.
|
||||
|
||||
@@ -375,10 +375,12 @@ UNIQUE: `(integration_id, shoppro_status_code)`
|
||||
| `last_run_at` | DATETIME | YES | |
|
||||
| `last_success_at` | DATETIME | YES | |
|
||||
| `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 | |
|
||||
| `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
|
||||
|
||||
---
|
||||
@@ -675,7 +677,7 @@ UNIQUE: `(integration_id)` - one global SMSPLANET settings row.
|
||||
| `created_at` | DATETIME | NO | DEFAULT 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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# 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
|
||||
|
||||
**Co zrobiono:**
|
||||
|
||||
@@ -56,6 +56,7 @@ final class ErliOrdersSyncService
|
||||
'acknowledged' => false,
|
||||
'acknowledged_count' => 0,
|
||||
'latest_message_id' => null,
|
||||
'last_error' => null,
|
||||
'errors' => [],
|
||||
];
|
||||
|
||||
@@ -119,10 +120,11 @@ final class ErliOrdersSyncService
|
||||
}
|
||||
|
||||
if ((int) $result['failed'] > 0) {
|
||||
$result['last_error'] = $this->buildBatchFailureMessage($result);
|
||||
$this->syncStateRepository->markRunFailed(
|
||||
$integrationId,
|
||||
new DateTimeImmutable('now'),
|
||||
'Erli import zakonczony z bledami: ' . (string) $result['failed']
|
||||
(string) $result['last_error']
|
||||
);
|
||||
return $result;
|
||||
}
|
||||
@@ -130,7 +132,9 @@ final class ErliOrdersSyncService
|
||||
if ($latestMessageId !== null) {
|
||||
$ack = $this->apiClient->markInboxRead($credentials, $latestMessageId);
|
||||
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_count'] = (int) ($ack['acknowledged_count'] ?? 0);
|
||||
@@ -170,6 +174,7 @@ final class ErliOrdersSyncService
|
||||
'acknowledged' => false,
|
||||
'acknowledged_count' => 0,
|
||||
'latest_message_id' => null,
|
||||
'last_error' => null,
|
||||
'errors' => [],
|
||||
];
|
||||
}
|
||||
@@ -306,8 +311,48 @@ final class ErliOrdersSyncService
|
||||
{
|
||||
$errors = is_array($result['errors'] ?? null) ? $result['errors'] : [];
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
229
tests/Unit/ErliOrdersSyncServiceTest.php
Normal file
229
tests/Unit/ErliOrdersSyncServiceTest.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,10 @@ final class ErliStatusSyncServiceTest extends TestCase
|
||||
self::assertSame(1, $result['pushed']);
|
||||
self::assertSame(1, $result['failed']);
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user