feat(50-allegro-shipment-waybill-push): push waybill to allegro checkout form
Phase 50 complete: - add conditional waybill push for allegro orders only - add retry on ALLEGRO_HTTP_401 and non-critical failure handling - add unit tests and update architecture/changelog docs
This commit is contained in:
@@ -13,7 +13,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
|||||||
| Attribute | Value |
|
| Attribute | Value |
|
||||||
|-----------|-------|
|
|-----------|-------|
|
||||||
| Version | 1.0.0 |
|
| Version | 1.0.0 |
|
||||||
| Status | v2.1 Complete |
|
| Status | v2.2 Complete |
|
||||||
| Last Updated | 2026-03-28 |
|
| Last Updated | 2026-03-28 |
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
@@ -58,6 +58,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
|||||||
- [x] Automatyzacja przesylek: natychmiastowy event `shipment.created` + akcja `update_shipment_status` - Phase 47
|
- [x] Automatyzacja przesylek: natychmiastowy event `shipment.created` + akcja `update_shipment_status` - Phase 47
|
||||||
- [x] Szablony e-mail: zmienne `przesylka.numer` i `przesylka.link_sledzenia` z provider-aware linkiem sledzenia - Phase 48
|
- [x] Szablony e-mail: zmienne `przesylka.numer` i `przesylka.link_sledzenia` z provider-aware linkiem sledzenia - Phase 48
|
||||||
- [x] Automatyzacja: tab Historia z filtrowaniem/paginacja + retencja 30 dni + akcja update_order_status - Phase 49
|
- [x] Automatyzacja: tab Historia z filtrowaniem/paginacja + retencja 30 dni + akcja update_order_status - Phase 49
|
||||||
|
- [x] Allegro: automatyczne przekazywanie numeru przesylki do checkout form po utworzeniu paczki (tylko source=allegro) - Phase 50
|
||||||
|
|
||||||
### Active (In Progress)
|
### Active (In Progress)
|
||||||
|
|
||||||
@@ -127,6 +128,7 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
|
|||||||
| Zmienne e-mail przesylki bazuja na najnowszej paczce `shipment_packages` i `DeliveryStatus::trackingUrl` | Jeden spojny kontrakt dla numeru i linku sledzenia w szablonach | 2026-03-28 | Active |
|
| Zmienne e-mail przesylki bazuja na najnowszej paczce `shipment_packages` i `DeliveryStatus::trackingUrl` | Jeden spojny kontrakt dla numeru i linku sledzenia w szablonach | 2026-03-28 | Active |
|
||||||
| Historia automatyzacji zapisywana per regula (success/failed) i czyszczona cronem po 30 dniach | Audyt wykonywania regul bez recznego utrzymania danych | 2026-03-28 | Active |
|
| Historia automatyzacji zapisywana per regula (success/failed) i czyszczona cronem po 30 dniach | Audyt wykonywania regul bez recznego utrzymania danych | 2026-03-28 | Active |
|
||||||
| Akcja update_order_status korzysta z OrdersRepository::updateOrderStatus | Spojnosc z historia statusow i activity log bez duplikowania logiki | 2026-03-28 | Active |
|
| Akcja update_order_status korzysta z OrdersRepository::updateOrderStatus | Spojnosc z historia statusow i activity log bez duplikowania logiki | 2026-03-28 | Active |
|
||||||
|
| Push waybilla do Allegro checkout forms wykonywany tylko dla zamowien source=allegro i jest niekrytyczny dla lokalnego tworzenia paczki | Eliminacja recznego kroku po stronie Allegro bez ryzyka utraty lokalnie utworzonej przesylki przy bledzie API | 2026-03-28 | Active |
|
||||||
|
|
||||||
## Success Metrics
|
## Success Metrics
|
||||||
|
|
||||||
@@ -158,6 +160,6 @@ Quick Reference:
|
|||||||
|
|
||||||
---
|
---
|
||||||
*PROJECT.md — Updated when requirements or context change*
|
*PROJECT.md — Updated when requirements or context change*
|
||||||
*Last updated: 2026-03-28 after Phase 49 completion (Automation History Tab)*
|
*Last updated: 2026-03-28 after Phase 50 completion (Allegro Shipment Waybill Push)*
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,19 @@ Next action: uruchom $paul-milestone (lub $paul-plan) dla kolejnego celu bizneso
|
|||||||
|
|
||||||
## Completed Milestones
|
## Completed Milestones
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>v2.2 Allegro Shipment Waybill Push - 2026-03-28 (1 phase, 1 plan)</summary>
|
||||||
|
|
||||||
|
Automatyczne przekazywanie waybilla do Allegro checkout forms przy tworzeniu przesylki, ograniczone do zamowien `source=allegro` i odporne na bledy API Allegro.
|
||||||
|
|
||||||
|
| Phase | Name | Plans | Completed |
|
||||||
|
|-------|------|-------|-----------|
|
||||||
|
| 50 | Allegro Shipment Waybill Push | 1/1 | 2026-03-28 |
|
||||||
|
|
||||||
|
Archive: `.paul/phases/50-allegro-shipment-waybill-push/`
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>v2.1 Automation History & Observability - 2026-03-28 (1 phase, 1 plan)</summary>
|
<summary>v2.1 Automation History & Observability - 2026-03-28 (1 phase, 1 plan)</summary>
|
||||||
|
|
||||||
@@ -302,7 +315,7 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
|
|||||||
|
|
||||||
---
|
---
|
||||||
*Roadmap created: 2026-03-12*
|
*Roadmap created: 2026-03-12*
|
||||||
*Last updated: 2026-03-28 - v2.1 completed (phase 49)*
|
*Last updated: 2026-03-28 - v2.2 completed (phase 50)*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
# Project State
|
# Project State
|
||||||
|
|
||||||
## Project Reference
|
## Project Reference
|
||||||
|
|
||||||
See: .paul/PROJECT.md (updated 2026-03-28)
|
See: .paul/PROJECT.md (updated 2026-03-28)
|
||||||
|
|
||||||
**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:** Milestone v2.1 completed; ready for next milestone planning
|
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
|
||||||
|
**Current focus:** Milestone v2.2 completed; ready for next PLAN / next milestone
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v2.1 Automation History & Observability - Complete
|
Milestone: v2.2 Allegro Shipment Waybill Push - Complete
|
||||||
Phase: 1 of 1 (49 - Automation History Tab) - Complete
|
Phase: 1 of 1 (50 - Allegro Shipment Waybill Push) - Complete
|
||||||
Plan: 49-01 complete
|
Plan: 50-01 complete
|
||||||
Status: Ready for next PLAN / next milestone
|
Status: Loop complete - ready for next PLAN
|
||||||
Last activity: 2026-03-28 14:47:06 - UNIFY closed for 49-01, SUMMARY created
|
Last activity: 2026-03-28 15:33:00 - UNIFY closed for 50-01, SUMMARY created
|
||||||
|
|
||||||
Progress:
|
Progress:
|
||||||
- Milestone: [##########] 100%
|
- Milestone: [##########] 100%
|
||||||
- Phase 49: [##########] 100%
|
- Phase 50: [##########] 100%
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
@@ -29,25 +30,19 @@ PLAN --> APPLY --> UNIFY
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-03-28 14:47:06
|
Last session: 2026-03-28 15:33:00
|
||||||
Stopped at: Phase 49 complete, milestone v2.1 complete
|
Stopped at: Phase 50 complete, milestone v2.2 complete
|
||||||
Next action: Uruchom `$paul-milestone` (lub `$paul-plan`) dla kolejnego celu
|
Next action: Uruchom $paul-milestone (lub $paul-plan) dla kolejnego celu
|
||||||
Resume file: .paul/ROADMAP.md
|
Resume file: .paul/phases/50-allegro-shipment-waybill-push/50-01-SUMMARY.md
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
### Decisions
|
### Decisions
|
||||||
| Date | Decision | Impact |
|
| Date | Decision | Impact |
|
||||||
|------|----------|--------|
|
|------|----------|--------|
|
||||||
| 2026-03-28 | Rozdzielenie `/settings/automation` na taby `Ustawienia` i `Historia` | Lepsza czytelnosc i oddzielenie konfiguracji od audytu wykonania |
|
| 2026-03-28 | Push waybilla do Allegro wykonywany tylko dla `orders.source='allegro'` i `source_order_id` | Brak falszywych pushy dla innych integracji |
|
||||||
| 2026-03-28 | Historia automatyzacji zapisywana per regula (success/failed) + filtry/paginacja | Szybsza diagnostyka triggerow i wynikow regul |
|
| 2026-03-28 | Blad pushu waybilla do Allegro jest niekrytyczny dla tworzenia paczki | Lokalna przesylka nie ginie przy problemie API Allegro |
|
||||||
| 2026-03-28 | Retencja historii starszej niz 30 dni przez cron `automation_history_cleanup` | Kontrola rozmiaru danych i automatyczne porzadkowanie |
|
| 2026-03-28 | Retry pushu po `ALLEGRO_HTTP_401` przez ponowne `resolveToken()` | Wyzsza odpornosc na wygasle tokeny |
|
||||||
| 2026-03-28 | Akcja `update_order_status` przez `OrdersRepository::updateOrderStatus` | Spojnosc z historia statusow i activity logiem |
|
|
||||||
|
|
||||||
### Skill Audit Carry-Over
|
|
||||||
| Expected | Invoked | Notes |
|
|
||||||
|----------|---------|-------|
|
|
||||||
| sonar-scanner | yes | Uruchomiony po APPLY, analiza zakonczona sukcesem |
|
|
||||||
|
|
||||||
## Git State
|
## Git State
|
||||||
|
|
||||||
|
|||||||
177
.paul/phases/50-allegro-shipment-waybill-push/50-01-PLAN.md
Normal file
177
.paul/phases/50-allegro-shipment-waybill-push/50-01-PLAN.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
---
|
||||||
|
phase: 50-allegro-shipment-waybill-push
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- src/Modules/Shipments/AllegroShipmentService.php
|
||||||
|
- tests/Unit/AllegroShipmentServiceTest.php
|
||||||
|
- DOCS/ARCHITECTURE.md
|
||||||
|
- DOCS/TECH_CHANGELOG.md
|
||||||
|
autonomous: true
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Dodac automatyczne przekazywanie numeru przesylki do Allegro w momencie skutecznego wygenerowania przesylki, ale tylko dla zamowien zrodla `allegro`.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Operator nie powinien recznie dopisywac numeru przesylki w Allegro po utworzeniu paczki w orderPRO, bo to spowalnia obsluge i zwieksza ryzyko pomylek.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
Rozszerzony flow `AllegroShipmentService` o push waybilla do endpointu checkout form shipments Allegro (z ochrona na przypadki spoza Allegro), testy jednostkowe nowej logiki oraz aktualizacja dokumentacji technicznej.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/ROADMAP.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
@DOCS/DB_SCHEMA.md
|
||||||
|
@DOCS/ARCHITECTURE.md
|
||||||
|
|
||||||
|
## Prior Work (only if genuinely needed)
|
||||||
|
@.paul/phases/46-allegro-status-push/46-01-SUMMARY.md
|
||||||
|
@.paul/phases/47-shipment-created-automation/47-01-SUMMARY.md
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@src/Modules/Shipments/AllegroShipmentService.php
|
||||||
|
@src/Modules/Settings/AllegroApiClient.php
|
||||||
|
@src/Modules/Orders/OrdersRepository.php
|
||||||
|
@src/Modules/Shipments/ShipmentPackageRepository.php
|
||||||
|
@tests/Unit/AllegroStatusSyncServiceTest.php
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<skills>
|
||||||
|
## Required Skills (from SPECIAL-FLOWS.md)
|
||||||
|
|
||||||
|
| Skill | Priority | When to Invoke | Loaded? |
|
||||||
|
|-------|----------|----------------|---------|
|
||||||
|
| `sonar-scanner` | required | Po APPLY, przed UNIFY | o |
|
||||||
|
| /feature-dev | optional | Przed wdrazaniem nowej funkcjonalnosci integracyjnej | o |
|
||||||
|
| /code-review | optional | Po APPLY, przed UNIFY | o |
|
||||||
|
|
||||||
|
**BLOCKING:** Required skills MUST be loaded before APPLY proceeds.
|
||||||
|
|
||||||
|
## Skill Invocation Checklist
|
||||||
|
- [ ] `sonar-scanner` uruchomiony po APPLY
|
||||||
|
- [ ] /feature-dev (opcjonalnie)
|
||||||
|
- [ ] /code-review (opcjonalnie)
|
||||||
|
|
||||||
|
</skills>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Numer przesylki jest wysylany do Allegro dla zamowienia Allegro
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie ma `source = allegro` i posiada `source_order_id`
|
||||||
|
When przesylka Allegro WZA zostanie utworzona i ma tracking number
|
||||||
|
Then system wywola API Allegro `POST /order/checkout-forms/{id}/shipments`
|
||||||
|
And do Allegro trafi waybill oraz dane przewoznika
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Zamowienia spoza Allegro nie wykonuja pushu do Allegro
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie ma inne zrodlo niz `allegro` (np. shoppro, erli)
|
||||||
|
When przesylka zostanie wygenerowana
|
||||||
|
Then system nie wywola endpointu pushu shipment do Allegro
|
||||||
|
And pozostale elementy flow tworzenia przesylki dzialaja bez zmian
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Blad pushu numeru do Allegro nie blokuje lokalnego procesu
|
||||||
|
```gherkin
|
||||||
|
Given przesylka lokalnie zostala utworzona i ma tracking number
|
||||||
|
When wywolanie endpointu Allegro zwroci blad lub wyjatek
|
||||||
|
Then rekord paczki w orderPRO pozostaje poprawnie zapisany
|
||||||
|
And uzytkownik nie traci utworzonej przesylki z powodu bledu pushu
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-4: Dokumentacja odzwierciedla nowy kontrakt
|
||||||
|
```gherkin
|
||||||
|
Given wdrozono push tracking number do Allegro checkout form shipments
|
||||||
|
When zespol czyta dokumentacje techniczna
|
||||||
|
Then `DOCS/ARCHITECTURE.md` i `DOCS/TECH_CHANGELOG.md` opisuja nowy moment i warunki pushu
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Rozszerz AllegroShipmentService o push waybill do checkout form shipments</name>
|
||||||
|
<files>src/Modules/Shipments/AllegroShipmentService.php</files>
|
||||||
|
<action>
|
||||||
|
Dodaj wewnetrzny flow, ktory po skutecznym utworzeniu przesylki i uzyskaniu tracking number wysyla numer do Allegro przez istniejace `AllegroApiClient::addShipmentToOrder(...)`.
|
||||||
|
Logike uruchamiaj tylko dla zamowien `source = allegro` oraz niepustego `source_order_id`.
|
||||||
|
Nie zmieniaj zachowania dla innych zrodel zamowien i providerow.
|
||||||
|
Blad pushu do Allegro traktuj jako niekrytyczny dla lokalnego lifecycle paczki (zapis paczki pozostaje sukcesem).
|
||||||
|
Utrzymaj retry po `ALLEGRO_HTTP_401` zgodnie z obecnym wzorcem token managera.
|
||||||
|
</action>
|
||||||
|
<verify>rg -n "addShipmentToOrder|source_order_id|source = 'allegro'|ALLEGRO_HTTP_401" src/Modules/Shipments/AllegroShipmentService.php</verify>
|
||||||
|
<done>AC-1 satisfied, AC-2 satisfied i AC-3 satisfied: push dziala tylko dla Allegro i nie psuje lokalnego flow przy bledzie API.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Dodaj testy jednostkowe dla scenariuszy pushu tracking number</name>
|
||||||
|
<files>tests/Unit/AllegroShipmentServiceTest.php</files>
|
||||||
|
<action>
|
||||||
|
Dodaj testy obejmujace co najmniej:
|
||||||
|
- wywolanie pushu dla zamowienia Allegro z tracking number,
|
||||||
|
- brak wywolania pushu dla zamowienia nie-Allegro,
|
||||||
|
- odporne zachowanie przy bledzie pushu (przesylka lokalnie pozostaje utworzona),
|
||||||
|
- retry po `ALLEGRO_HTTP_401`.
|
||||||
|
Mockuj zaleznosci zgodnie z obecnym stylem testow (`PHPUnit` + `dg/bypass-finals`).
|
||||||
|
</action>
|
||||||
|
<verify>C:/xampp/php/php.exe vendor/bin/phpunit --filter AllegroShipmentServiceTest --testdox</verify>
|
||||||
|
<done>AC-1 satisfied, AC-2 satisfied i AC-3 satisfied: testy potwierdzaja warunki i odpornosc implementacji.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Zaktualizuj dokumentacje techniczna</name>
|
||||||
|
<files>DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
|
||||||
|
<action>
|
||||||
|
Opisz nowy krok przeplywu tworzenia przesylki Allegro: automatyczne przekazanie waybilla do checkout form shipment po uzyskaniu tracking number.
|
||||||
|
Dodaj informacje o warunku ograniczajacym zakres do zamowien zrodla Allegro i o fallbacku na niekrytyczny blad pushu.
|
||||||
|
Potwierdz brak zmian w schemacie bazy.
|
||||||
|
</action>
|
||||||
|
<verify>rg -n "checkout-forms/.*/shipments|waybill|source = allegro|tracking number" DOCS/ARCHITECTURE.md DOCS/TECH_CHANGELOG.md</verify>
|
||||||
|
<done>AC-4 satisfied: dokumentacja odzwierciedla nowa logike i ograniczenia zakresu.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- `database/migrations/*` (brak zmian schematu DB)
|
||||||
|
- Flow tworzenia przesylek dla providerow innych niz `allegro_wza`
|
||||||
|
- Logika status sync orderPRO -> Allegro z fazy 46 (to osobny mechanizm)
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- Zakres obejmuje tylko push numeru przesylki do Allegro przy generowaniu przesylki.
|
||||||
|
- Bez zmian UI i bez nowych formularzy.
|
||||||
|
- Bez dodawania nowego cron joba.
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Before declaring plan complete:
|
||||||
|
- [ ] `C:\xampp\php\php.exe -l src/Modules/Shipments/AllegroShipmentService.php`
|
||||||
|
- [ ] `C:\xampp\php\php.exe -l tests/Unit/AllegroShipmentServiceTest.php`
|
||||||
|
- [ ] `C:\xampp\php\php.exe vendor/bin/phpunit --filter AllegroShipmentServiceTest --testdox`
|
||||||
|
- [ ] Brak regresji lokalnego flow tworzenia paczki dla zamowien nie-Allegro
|
||||||
|
- [ ] Dokumentacja zaktualizowana (`DOCS/ARCHITECTURE.md`, `DOCS/TECH_CHANGELOG.md`)
|
||||||
|
- [ ] All acceptance criteria met
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Po wygenerowaniu przesylki dla zamowienia Allegro system automatycznie wysyla numer przesylki do Allegro.
|
||||||
|
- Dla zamowien spoza Allegro nie ma wywolan endpointu Allegro shipment push.
|
||||||
|
- Ewentualny blad pushu nie anuluje lokalnie utworzonej przesylki.
|
||||||
|
- Dokumentacja opisuje nowy kontrakt i brak zmian DB.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.paul/phases/50-allegro-shipment-waybill-push/50-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
phase: 50-allegro-shipment-waybill-push
|
||||||
|
plan: 01
|
||||||
|
status: completed
|
||||||
|
completed: 2026-03-28
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 50 Plan 01 Summary
|
||||||
|
|
||||||
|
## Result
|
||||||
|
- Dodano automatyczny push numeru przesylki do Allegro (`POST /order/checkout-forms/{id}/shipments`) po uzyskaniu `tracking_number`.
|
||||||
|
- Push wykonuje sie tylko dla zamowien Allegro (`orders.source='allegro'`) z niepustym `source_order_id`.
|
||||||
|
- Blad pushu jest niekrytyczny - lokalna paczka pozostaje utworzona.
|
||||||
|
- Dodano retry pushu po `ALLEGRO_HTTP_401` z odswiezeniem tokenu.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- AC-1: Pass
|
||||||
|
- AC-2: Pass
|
||||||
|
- AC-3: Pass
|
||||||
|
- AC-4: Pass
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
- `C:\xampp\php\php.exe -l src/Modules/Shipments/AllegroShipmentService.php` PASS
|
||||||
|
- `C:\xampp\php\php.exe -l tests/Unit/AllegroShipmentServiceTest.php` PASS
|
||||||
|
- `C:\xampp\php\php.exe vendor/bin/phpunit --filter AllegroShipmentServiceTest --testdox` PASS (4 tests, 54 assertions)
|
||||||
|
- `sonar-scanner` PASS (analysis successful): https://sonar.project-pro.pl/dashboard?id=orderPRO
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `src/Modules/Shipments/AllegroShipmentService.php`
|
||||||
|
- `tests/Unit/AllegroShipmentServiceTest.php`
|
||||||
|
- `DOCS/ARCHITECTURE.md`
|
||||||
|
- `DOCS/TECH_CHANGELOG.md`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Brak zmian schematu DB.
|
||||||
|
- Brak checkpointow manualnych (plan autonomiczny).
|
||||||
@@ -341,6 +341,9 @@
|
|||||||
- `GET /orders/{id}/shipment/{packageId}/status`:
|
- `GET /orders/{id}/shipment/{packageId}/status`:
|
||||||
- `ShipmentController::checkStatus(Request): Response`,
|
- `ShipmentController::checkStatus(Request): Response`,
|
||||||
- wybiera providera po `shipment_packages.provider` i deleguje `checkCreationStatus(...)`.
|
- wybiera providera po `shipment_packages.provider` i deleguje `checkCreationStatus(...)`.
|
||||||
|
- dla providera `allegro_wza` po uzyskaniu `tracking_number` serwis probuje przekazac waybill do Allegro przez `POST /order/checkout-forms/{id}/shipments` (`AllegroApiClient::addShipmentToOrder(...)`),
|
||||||
|
- push waybilla jest wykonywany tylko dla zamowien `orders.source='allegro'` i niepustego `orders.source_order_id`,
|
||||||
|
- blad pushu waybilla do Allegro nie przerywa lokalnego flow tworzenia paczki (fallback niekrytyczny, paczka zostaje zapisana w orderPRO).
|
||||||
- `POST /orders/{id}/shipment/{packageId}/label`:
|
- `POST /orders/{id}/shipment/{packageId}/label`:
|
||||||
- `ShipmentController::label(Request): Response`,
|
- `ShipmentController::label(Request): Response`,
|
||||||
- wybiera providera po `shipment_packages.provider` i deleguje `downloadLabel(...)`,
|
- wybiera providera po `shipment_packages.provider` i deleguje `downloadLabel(...)`,
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# Tech Changelog
|
# Tech Changelog
|
||||||
|
|
||||||
|
## 2026-03-28 (Phase 50 - Allegro Shipment Waybill Push, Plan 01)
|
||||||
|
- `AllegroShipmentService`:
|
||||||
|
- po sukcesie `checkCreationStatus(...)` (gdy jest `tracking_number`) probuje dopiac przesylke do checkout form Allegro,
|
||||||
|
- wykorzystuje `AllegroApiClient::addShipmentToOrder(...)` (`POST /order/checkout-forms/{id}/shipments`),
|
||||||
|
- push wykonywany tylko dla zamowien `orders.source='allegro'` i niepustego `source_order_id`,
|
||||||
|
- retry pushu po `ALLEGRO_HTTP_401` z ponownym `tokenManager->resolveToken()`,
|
||||||
|
- bledy pushu traktowane jako niekrytyczne (lokalna paczka pozostaje utworzona).
|
||||||
|
- `AllegroShipmentService::downloadLabel(...)`:
|
||||||
|
- przy fallbackowym dopelnieniu trackingu (gdy brak numeru w rekordzie paczki) wykonuje ten sam warunkowy push waybilla do Allegro.
|
||||||
|
- Testy:
|
||||||
|
- dodano `tests/Unit/AllegroShipmentServiceTest.php` (scenariusze: push dla Allegro, brak pushu dla nie-Allegro, fallback przy bledzie API, retry po 401).
|
||||||
|
|
||||||
## 2026-03-28 (Public HTTPS cron endpoint)
|
## 2026-03-28 (Public HTTPS cron endpoint)
|
||||||
- Dodano publiczny endpoint triggera crona:
|
- Dodano publiczny endpoint triggera crona:
|
||||||
- `GET /cron?token=<CRON_PUBLIC_TOKEN>`
|
- `GET /cron?token=<CRON_PUBLIC_TOKEN>`
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Modules\Shipments;
|
namespace App\Modules\Shipments;
|
||||||
|
|
||||||
|
use App\Core\Exceptions\IntegrationConfigException;
|
||||||
|
use App\Core\Exceptions\ShipmentException;
|
||||||
use App\Modules\Orders\OrdersRepository;
|
use App\Modules\Orders\OrdersRepository;
|
||||||
use App\Modules\Settings\AllegroApiClient;
|
use App\Modules\Settings\AllegroApiClient;
|
||||||
use App\Modules\Settings\AllegroTokenManager;
|
use App\Modules\Settings\AllegroTokenManager;
|
||||||
use App\Modules\Settings\CompanySettingsRepository;
|
use App\Modules\Settings\CompanySettingsRepository;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use App\Core\Exceptions\IntegrationConfigException;
|
|
||||||
use App\Core\Exceptions\ShipmentException;
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
final class AllegroShipmentService implements ShipmentProviderInterface
|
final class AllegroShipmentService implements ShipmentProviderInterface
|
||||||
@@ -35,6 +35,7 @@ final class AllegroShipmentService implements ShipmentProviderInterface
|
|||||||
{
|
{
|
||||||
[$accessToken, $env] = $this->tokenManager->resolveToken();
|
[$accessToken, $env] = $this->tokenManager->resolveToken();
|
||||||
$response = $this->apiClient->getDeliveryServices($env, $accessToken);
|
$response = $this->apiClient->getDeliveryServices($env, $accessToken);
|
||||||
|
|
||||||
return is_array($response['services'] ?? null) ? $response['services'] : [];
|
return is_array($response['services'] ?? null) ? $response['services'] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ final class AllegroShipmentService implements ShipmentProviderInterface
|
|||||||
|
|
||||||
$codAmount = (float) ($formData['cod_amount'] ?? 0);
|
$codAmount = (float) ($formData['cod_amount'] ?? 0);
|
||||||
if ($codAmount > 0) {
|
if ($codAmount > 0) {
|
||||||
// Allegro WZA manages COD funds internally – iban/ownerName are not accepted
|
// Allegro WZA manages COD funds internally - iban/ownerName are not accepted.
|
||||||
$apiPayload['input']['cashOnDelivery'] = [
|
$apiPayload['input']['cashOnDelivery'] = [
|
||||||
'amount' => number_format($codAmount, 2, '.', ''),
|
'amount' => number_format($codAmount, 2, '.', ''),
|
||||||
'currency' => strtoupper(trim((string) ($formData['cod_currency'] ?? 'PLN'))),
|
'currency' => strtoupper(trim((string) ($formData['cod_currency'] ?? 'PLN'))),
|
||||||
@@ -196,14 +197,27 @@ final class AllegroShipmentService implements ShipmentProviderInterface
|
|||||||
if ($status === 'SUCCESS' && $shipmentId !== '') {
|
if ($status === 'SUCCESS' && $shipmentId !== '') {
|
||||||
$details = $this->apiClient->getShipmentDetails($env, $accessToken, $shipmentId);
|
$details = $this->apiClient->getShipmentDetails($env, $accessToken, $shipmentId);
|
||||||
$trackingNumber = trim((string) ($details['waybill'] ?? ''));
|
$trackingNumber = trim((string) ($details['waybill'] ?? ''));
|
||||||
|
$carrierId = trim((string) ($package['carrier_id'] ?? ''));
|
||||||
|
if ($carrierId === '') {
|
||||||
|
$carrierId = trim((string) ($details['carrierId'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
$this->packages->update($packageId, [
|
$this->packages->update($packageId, [
|
||||||
'status' => 'created',
|
'status' => 'created',
|
||||||
'shipment_id' => $shipmentId,
|
'shipment_id' => $shipmentId,
|
||||||
'tracking_number' => $trackingNumber !== '' ? $trackingNumber : null,
|
'tracking_number' => $trackingNumber !== '' ? $trackingNumber : null,
|
||||||
|
'carrier_id' => $carrierId !== '' ? $carrierId : ($package['carrier_id'] ?? null),
|
||||||
'payload_json' => $details,
|
'payload_json' => $details,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->pushWaybillToAllegroCheckoutForm(
|
||||||
|
(int) ($package['order_id'] ?? 0),
|
||||||
|
$trackingNumber,
|
||||||
|
$carrierId,
|
||||||
|
$accessToken,
|
||||||
|
$env
|
||||||
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'status' => 'created',
|
'status' => 'created',
|
||||||
'shipment_id' => $shipmentId,
|
'shipment_id' => $shipmentId,
|
||||||
@@ -268,16 +282,32 @@ final class AllegroShipmentService implements ShipmentProviderInterface
|
|||||||
'label_path' => 'labels/' . $filename,
|
'label_path' => 'labels/' . $filename,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Refresh tracking number if not yet saved (may not have been available at creation time)
|
// Refresh tracking number if not yet saved (may not have been available at creation time).
|
||||||
if (trim((string) ($package['tracking_number'] ?? '')) === '') {
|
if (trim((string) ($package['tracking_number'] ?? '')) === '') {
|
||||||
try {
|
try {
|
||||||
$details = $this->apiClient->getShipmentDetails($env, $accessToken, $shipmentId);
|
$details = $this->apiClient->getShipmentDetails($env, $accessToken, $shipmentId);
|
||||||
$trackingNumber = trim((string) ($details['waybill'] ?? ''));
|
$trackingNumber = trim((string) ($details['waybill'] ?? ''));
|
||||||
|
$carrierId = trim((string) ($package['carrier_id'] ?? ''));
|
||||||
|
if ($carrierId === '') {
|
||||||
|
$carrierId = trim((string) ($details['carrierId'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
if ($trackingNumber !== '') {
|
if ($trackingNumber !== '') {
|
||||||
$updateFields['tracking_number'] = $trackingNumber;
|
$updateFields['tracking_number'] = $trackingNumber;
|
||||||
|
if ($carrierId !== '') {
|
||||||
|
$updateFields['carrier_id'] = $carrierId;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pushWaybillToAllegroCheckoutForm(
|
||||||
|
(int) ($package['order_id'] ?? 0),
|
||||||
|
$trackingNumber,
|
||||||
|
$carrierId,
|
||||||
|
$accessToken,
|
||||||
|
$env
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (Throwable) {
|
} catch (Throwable) {
|
||||||
// non-critical – label is still saved
|
// non-critical - label is still saved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,11 +389,87 @@ final class AllegroShipmentService implements ShipmentProviderInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function pushWaybillToAllegroCheckoutForm(
|
||||||
|
int $orderId,
|
||||||
|
string $trackingNumber,
|
||||||
|
string $carrierId,
|
||||||
|
string $accessToken,
|
||||||
|
string $environment
|
||||||
|
): void {
|
||||||
|
if ($orderId <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$waybill = trim($trackingNumber);
|
||||||
|
if ($waybill === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderDetails = $this->ordersRepository->findDetails($orderId);
|
||||||
|
if ($orderDetails === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$order = is_array($orderDetails['order'] ?? null) ? $orderDetails['order'] : [];
|
||||||
|
$source = strtolower(trim((string) ($order['source'] ?? '')));
|
||||||
|
if ($source !== 'allegro') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$checkoutFormId = trim((string) ($order['source_order_id'] ?? ''));
|
||||||
|
if ($checkoutFormId === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalizedCarrierId = trim($carrierId);
|
||||||
|
if ($normalizedCarrierId === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$carrierName = (string) ($this->packages->resolveCarrierName('allegro_wza', $normalizedCarrierId) ?? '');
|
||||||
|
$carrierName = trim($carrierName);
|
||||||
|
if ($carrierName === '') {
|
||||||
|
$carrierName = $normalizedCarrierId;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->apiClient->addShipmentToOrder(
|
||||||
|
$environment,
|
||||||
|
$accessToken,
|
||||||
|
$checkoutFormId,
|
||||||
|
$waybill,
|
||||||
|
$normalizedCarrierId,
|
||||||
|
$carrierName
|
||||||
|
);
|
||||||
|
} catch (RuntimeException $exception) {
|
||||||
|
if (trim($exception->getMessage()) !== 'ALLEGRO_HTTP_401') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
[$refreshedToken, $refreshedEnvironment] = $this->tokenManager->resolveToken();
|
||||||
|
$this->apiClient->addShipmentToOrder(
|
||||||
|
$refreshedEnvironment,
|
||||||
|
$refreshedToken,
|
||||||
|
$checkoutFormId,
|
||||||
|
$waybill,
|
||||||
|
$normalizedCarrierId,
|
||||||
|
$carrierName
|
||||||
|
);
|
||||||
|
} catch (Throwable) {
|
||||||
|
// non-critical - local shipment remains created
|
||||||
|
}
|
||||||
|
} catch (Throwable) {
|
||||||
|
// non-critical - local shipment remains created
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function generateUuid(): string
|
private function generateUuid(): string
|
||||||
{
|
{
|
||||||
$data = random_bytes(16);
|
$data = random_bytes(16);
|
||||||
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
||||||
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
|
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
|
||||||
|
|
||||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
322
tests/Unit/AllegroShipmentServiceTest.php
Normal file
322
tests/Unit/AllegroShipmentServiceTest.php
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use App\Modules\Orders\OrdersRepository;
|
||||||
|
use App\Modules\Settings\AllegroApiClient;
|
||||||
|
use App\Modules\Settings\AllegroTokenManager;
|
||||||
|
use App\Modules\Settings\CompanySettingsRepository;
|
||||||
|
use App\Modules\Shipments\AllegroShipmentService;
|
||||||
|
use App\Modules\Shipments\ShipmentPackageRepository;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
final class AllegroShipmentServiceTest extends TestCase
|
||||||
|
{
|
||||||
|
private AllegroTokenManager&MockObject $tokenManager;
|
||||||
|
private AllegroApiClient&MockObject $apiClient;
|
||||||
|
private ShipmentPackageRepository&MockObject $packages;
|
||||||
|
private CompanySettingsRepository&MockObject $companySettings;
|
||||||
|
private OrdersRepository&MockObject $ordersRepository;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->tokenManager = $this->createMock(AllegroTokenManager::class);
|
||||||
|
$this->apiClient = $this->createMock(AllegroApiClient::class);
|
||||||
|
$this->packages = $this->createMock(ShipmentPackageRepository::class);
|
||||||
|
$this->companySettings = $this->createMock(CompanySettingsRepository::class);
|
||||||
|
$this->ordersRepository = $this->createMock(OrdersRepository::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCheckCreationStatusPushesWaybillForAllegroOrder(): void
|
||||||
|
{
|
||||||
|
$this->tokenManager
|
||||||
|
->method('resolveToken')
|
||||||
|
->willReturn(['token-1', 'sandbox']);
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->method('findById')
|
||||||
|
->with(11)
|
||||||
|
->willReturn([
|
||||||
|
'id' => 11,
|
||||||
|
'order_id' => 200,
|
||||||
|
'command_id' => 'cmd-11',
|
||||||
|
'carrier_id' => 'INPOST',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->expects($this->once())
|
||||||
|
->method('update')
|
||||||
|
->with(
|
||||||
|
11,
|
||||||
|
$this->callback(static function (array $payload): bool {
|
||||||
|
return ($payload['tracking_number'] ?? null) === 'TRK-001'
|
||||||
|
&& ($payload['carrier_id'] ?? null) === 'INPOST';
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->expects($this->once())
|
||||||
|
->method('resolveCarrierName')
|
||||||
|
->with('allegro_wza', 'INPOST')
|
||||||
|
->willReturn('InPost');
|
||||||
|
|
||||||
|
$this->ordersRepository
|
||||||
|
->method('findDetails')
|
||||||
|
->with(200)
|
||||||
|
->willReturn([
|
||||||
|
'order' => [
|
||||||
|
'source' => 'allegro',
|
||||||
|
'source_order_id' => 'CHECKOUT-200',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->method('getShipmentCreationStatus')
|
||||||
|
->with('sandbox', 'token-1', 'cmd-11')
|
||||||
|
->willReturn([
|
||||||
|
'status' => 'SUCCESS',
|
||||||
|
'shipmentId' => 'SHIP-11',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->method('getShipmentDetails')
|
||||||
|
->with('sandbox', 'token-1', 'SHIP-11')
|
||||||
|
->willReturn([
|
||||||
|
'waybill' => 'TRK-001',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->expects($this->once())
|
||||||
|
->method('addShipmentToOrder')
|
||||||
|
->with(
|
||||||
|
'sandbox',
|
||||||
|
'token-1',
|
||||||
|
'CHECKOUT-200',
|
||||||
|
'TRK-001',
|
||||||
|
'INPOST',
|
||||||
|
'InPost'
|
||||||
|
)
|
||||||
|
->willReturn([]);
|
||||||
|
|
||||||
|
$service = $this->createService();
|
||||||
|
$result = $service->checkCreationStatus(11);
|
||||||
|
|
||||||
|
$this->assertSame('created', $result['status']);
|
||||||
|
$this->assertSame('TRK-001', $result['tracking_number']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCheckCreationStatusSkipsPushForNonAllegroOrder(): void
|
||||||
|
{
|
||||||
|
$this->tokenManager
|
||||||
|
->method('resolveToken')
|
||||||
|
->willReturn(['token-1', 'sandbox']);
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->method('findById')
|
||||||
|
->with(12)
|
||||||
|
->willReturn([
|
||||||
|
'id' => 12,
|
||||||
|
'order_id' => 201,
|
||||||
|
'command_id' => 'cmd-12',
|
||||||
|
'carrier_id' => 'DPD',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->expects($this->once())
|
||||||
|
->method('update');
|
||||||
|
|
||||||
|
$this->ordersRepository
|
||||||
|
->method('findDetails')
|
||||||
|
->with(201)
|
||||||
|
->willReturn([
|
||||||
|
'order' => [
|
||||||
|
'source' => 'shoppro',
|
||||||
|
'source_order_id' => 'SP-ORDER-1',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->method('getShipmentCreationStatus')
|
||||||
|
->willReturn([
|
||||||
|
'status' => 'SUCCESS',
|
||||||
|
'shipmentId' => 'SHIP-12',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->method('getShipmentDetails')
|
||||||
|
->willReturn([
|
||||||
|
'waybill' => 'TRK-002',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->expects($this->never())
|
||||||
|
->method('addShipmentToOrder');
|
||||||
|
|
||||||
|
$service = $this->createService();
|
||||||
|
$result = $service->checkCreationStatus(12);
|
||||||
|
|
||||||
|
$this->assertSame('created', $result['status']);
|
||||||
|
$this->assertSame('TRK-002', $result['tracking_number']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCheckCreationStatusDoesNotFailWhenPushReturnsError(): void
|
||||||
|
{
|
||||||
|
$this->tokenManager
|
||||||
|
->method('resolveToken')
|
||||||
|
->willReturn(['token-1', 'sandbox']);
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->method('findById')
|
||||||
|
->with(13)
|
||||||
|
->willReturn([
|
||||||
|
'id' => 13,
|
||||||
|
'order_id' => 202,
|
||||||
|
'command_id' => 'cmd-13',
|
||||||
|
'carrier_id' => 'DHL',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->expects($this->once())
|
||||||
|
->method('update');
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->method('resolveCarrierName')
|
||||||
|
->willReturn('DHL');
|
||||||
|
|
||||||
|
$this->ordersRepository
|
||||||
|
->method('findDetails')
|
||||||
|
->with(202)
|
||||||
|
->willReturn([
|
||||||
|
'order' => [
|
||||||
|
'source' => 'allegro',
|
||||||
|
'source_order_id' => 'CHECKOUT-202',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->method('getShipmentCreationStatus')
|
||||||
|
->willReturn([
|
||||||
|
'status' => 'SUCCESS',
|
||||||
|
'shipmentId' => 'SHIP-13',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->method('getShipmentDetails')
|
||||||
|
->willReturn([
|
||||||
|
'waybill' => 'TRK-003',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->expects($this->once())
|
||||||
|
->method('addShipmentToOrder')
|
||||||
|
->willThrowException(new RuntimeException('API Allegro HTTP 422'));
|
||||||
|
|
||||||
|
$service = $this->createService();
|
||||||
|
$result = $service->checkCreationStatus(13);
|
||||||
|
|
||||||
|
$this->assertSame('created', $result['status']);
|
||||||
|
$this->assertSame('TRK-003', $result['tracking_number']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCheckCreationStatusRetriesPushAfter401(): void
|
||||||
|
{
|
||||||
|
$this->tokenManager
|
||||||
|
->method('resolveToken')
|
||||||
|
->willReturnOnConsecutiveCalls(
|
||||||
|
['token-old', 'sandbox'],
|
||||||
|
['token-new', 'production']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->method('findById')
|
||||||
|
->with(14)
|
||||||
|
->willReturn([
|
||||||
|
'id' => 14,
|
||||||
|
'order_id' => 203,
|
||||||
|
'command_id' => 'cmd-14',
|
||||||
|
'carrier_id' => 'INPOST',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->expects($this->once())
|
||||||
|
->method('update');
|
||||||
|
|
||||||
|
$this->packages
|
||||||
|
->method('resolveCarrierName')
|
||||||
|
->willReturn('InPost');
|
||||||
|
|
||||||
|
$this->ordersRepository
|
||||||
|
->method('findDetails')
|
||||||
|
->with(203)
|
||||||
|
->willReturn([
|
||||||
|
'order' => [
|
||||||
|
'source' => 'allegro',
|
||||||
|
'source_order_id' => 'CHECKOUT-203',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->method('getShipmentCreationStatus')
|
||||||
|
->with('sandbox', 'token-old', 'cmd-14')
|
||||||
|
->willReturn([
|
||||||
|
'status' => 'SUCCESS',
|
||||||
|
'shipmentId' => 'SHIP-14',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->method('getShipmentDetails')
|
||||||
|
->with('sandbox', 'token-old', 'SHIP-14')
|
||||||
|
->willReturn([
|
||||||
|
'waybill' => 'TRK-004',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->apiClient
|
||||||
|
->expects($this->exactly(2))
|
||||||
|
->method('addShipmentToOrder')
|
||||||
|
->willReturnCallback(static function (
|
||||||
|
string $environment,
|
||||||
|
string $token,
|
||||||
|
string $checkoutId,
|
||||||
|
string $waybill,
|
||||||
|
string $carrierId,
|
||||||
|
string $carrierName
|
||||||
|
): array {
|
||||||
|
static $calls = 0;
|
||||||
|
$calls++;
|
||||||
|
|
||||||
|
if ($calls === 1) {
|
||||||
|
TestCase::assertSame('sandbox', $environment);
|
||||||
|
TestCase::assertSame('token-old', $token);
|
||||||
|
TestCase::assertSame('CHECKOUT-203', $checkoutId);
|
||||||
|
TestCase::assertSame('TRK-004', $waybill);
|
||||||
|
TestCase::assertSame('INPOST', $carrierId);
|
||||||
|
TestCase::assertSame('InPost', $carrierName);
|
||||||
|
throw new RuntimeException('ALLEGRO_HTTP_401');
|
||||||
|
}
|
||||||
|
|
||||||
|
TestCase::assertSame('production', $environment);
|
||||||
|
TestCase::assertSame('token-new', $token);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
$service = $this->createService();
|
||||||
|
$result = $service->checkCreationStatus(14);
|
||||||
|
|
||||||
|
$this->assertSame('created', $result['status']);
|
||||||
|
$this->assertSame('TRK-004', $result['tracking_number']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createService(): AllegroShipmentService
|
||||||
|
{
|
||||||
|
return new AllegroShipmentService(
|
||||||
|
$this->tokenManager,
|
||||||
|
$this->apiClient,
|
||||||
|
$this->packages,
|
||||||
|
$this->companySettings,
|
||||||
|
$this->ordersRepository
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user