feat(v1.7): orderPRO -> shopPRO status push sync

Implement bidirectional status sync for shopPRO integrations.
When direction is set to orderpro_to_shoppro, cron pushes manual
status changes to shopPRO via PUT API with reverse status mapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 12:54:57 +01:00
parent 054816b0ba
commit 957fddaf84
13 changed files with 867 additions and 41 deletions

View File

@@ -13,7 +13,7 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
| Attribute | Value |
|-----------|-------|
| Version | 1.0.0 |
| Status | v1.6 Complete |
| Status | v1.7 Complete |
| Last Updated | 2026-03-27 |
## Requirements
@@ -53,6 +53,7 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
- [x] Automatyzacja: event `shipment.status_changed` + warunki statusowe przesylki - Phase 42
- [x] Usuwanie wpisu z kolejki druku etykiet z panelu ustawien - Phase 43
- [x] Szybka zmiana statusu zamowienia z listy zamowien (inline dropdown + AJAX) - Phase 44
- [x] Synchronizacja statusow orderPRO -> shopPRO (cron push, reverse mapping, PUT API) — Phase 45
### Active (In Progress)
@@ -148,5 +149,5 @@ Quick Reference:
---
*PROJECT.md — Updated when requirements or context change*
*Last updated: 2026-03-27 after Phase 44 completion (Inline Status Change)*
*Last updated: 2026-03-27 after Phase 45 completion (ShopPRO Status Push)*

View File

@@ -6,17 +6,30 @@ orderPRO to narzÄ™dzie do wielokanaĹowego zarzÄ…dzania sprzedaĹĽÄ
## Current Milestone
v1.6 Quick Status Change - Complete (2026-03-27)
v1.7 ShopPRO Status Push - Complete (2026-03-27)
Szybka zmiana statusu zamówienia bezpośrednio z listy zamówień klikalny dropdown w kolumnie statusu, zmiana przez AJAX bez przeładowania strony.
Implementacja synchronizacji statusów zamówień w kierunku orderPRO → shopPRO. Cron pushuje zmiany statusów do shopPRO API (PUT /api.php?endpoint=orders&action=change_status).
| Phase | Name | Status | Plans |
|------|------|--------|-------|
| 44 | Inline Status Change | Complete (2026-03-27) | 1/1 (`44-01-PLAN.md`) |
| 45 | ShopPRO Status Push | Complete (2026-03-27) | 1/1 (`45-01-PLAN.md`) |
Archive: `.paul/phases/45-shoppro-status-push/`
## Completed Milestones
<details>
<summary>v1.6 Quick Status Change - 2026-03-27 (1 phase, 1 plan)</summary>
Szybka zmiana statusu zamówienia bezpośrednio z listy zamówień — klikalny dropdown w kolumnie statusu, zmiana przez AJAX bez przeładowania strony.
| Phase | Name | Plans | Completed |
|-------|------|-------|-----------|
| 44 | Inline Status Change | 1/1 | 2026-03-27 |
Archive: `.paul/phases/44-inline-status-change/`
## Completed Milestones
</details>
<details>
<summary>v1.5 Operational Workflow Cleanup - 2026-03-25 (4 phases, 4 plans)</summary>
@@ -229,7 +242,7 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
---
*Roadmap created: 2026-03-12*
*Last updated: 2026-03-27 - v1.6 Quick Status Change complete*
*Last updated: 2026-03-27 - v1.7 ShopPRO Status Push complete*

View File

@@ -5,15 +5,15 @@
See: .paul/PROJECT.md (updated 2026-03-12)
**Core value:** Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw sprzedaĹĽy i nadawać przesyĹki bez przeĹÄ…czania siÄ™ miÄ™dzy platformami.
**Current focus:** v1.6 complete — Phase 44 delivered
**Current focus:** v1.7 complete — Phase 45 delivered
## Current Position
Milestone: v1.6 Quick Status Change — Complete
Phase: [1] of [1] (Inline Status Change) — Unified
Plan: 44-01 completed with summary
Status: PLAN/APPLY/UNIFY closed for phase 44
Last activity: 2026-03-27 — Phase 44 complete, milestone v1.6 closed
Milestone: v1.7 ShopPRO Status Push — Complete
Phase: [1] of [1] (ShopPRO Status Push) — Unified
Plan: 45-01 completed with summary
Status: PLAN/APPLY/UNIFY closed for phase 45
Last activity: 2026-03-27 — Phase 45 complete, milestone v1.7 closed
Progress:
- v0.1 Initial Release: [##########] 100% done
@@ -41,20 +41,25 @@ Progress:
- Phase 43: [##########] Complete (1/1 plans)
- v1.6 Quick Status Change: [##########] 100% done
- Phase 44: [##########] Complete (1/1 plans)
- v1.7 ShopPRO Status Push: [##########] 100% done
- Phase 45: [##########] Complete (1/1 plans)
## Loop Position
Current loop state:
```
PLAN --> APPLY --> UNIFY
done done done [Loop closed for phase 44]
done done done [Loop closed for phase 45]
```
## Accumulated Context
### Decisions
| Data | Decyzja | Faza | Wpływ |
| Data | Decyzja | Faza | Wpływ |
|------|---------|------|-------|
| 2026-03-27 | Refactor executeRequest() w ShopproApiClient zamiast duplikacji curl logic | Faza 45 | Reuse GET/PUT, latwiejsze dodawanie metod HTTP |
| 2026-03-27 | Push tylko change_source=manual (nie import/sync) | Faza 45 | Brak petli synchronizacji |
| 2026-03-27 | Fallback 24h dla null cursor last_status_pushed_at | Faza 45 | Ograniczenie zakresu pierwszego synca |
| 2026-03-27 | Fixed positioning dropdown (document.body) zamiast absolute wewnatrz table-wrap | Faza 44 | Dropdown nie ucinany przez overflow:hidden na .table-wrap |
| 2026-03-27 | AJAX detect przez X-Requested-With header z fallback na redirect | Faza 44 | updateStatus() obsluguje oba tryby w jednej metodzie |
| 2026-03-25 | Import Allegro: trigger context + deduplikacja logow (`source_order_id + source_updated_at + trigger`) | Faza 41 | Czytelniejsza historia zamowienia i mniej duplikatow wpisow `import` |
@@ -92,6 +97,11 @@ PLAN --> APPLY --> UNIFY
| 2026-03-17 | Email history jako wpisy w order_activity_log (nie osobna sekcja) | Faza 15 | SpĂłjnoĹć z istniejÄ…cym UX — jeden timeline zamiast fragmentacji |
| 2026-03-17 | VariableResolver wydzielony z EmailTemplateController | Faza 15 | Reuse logiki zmiennych; resolwer niezaleĹĽny od kontrolera szablonĂłw |
### Skill Audit (Faza 45, Plan 01)
| Oczekiwany | Wywolany | Uwagi |
|------------|---------|-------|
| sonar-scanner | override | Pominieto na podstawie explicit user override; lint PHP PASS |
### Skill Audit (Faza 44, Plan 01)
| Oczekiwany | Wywolany | Uwagi |
|------------|---------|-------|
@@ -291,7 +301,7 @@ Brak.
## Session Continuity
Last session: 2026-03-27
Stopped at: v1.6 phase 44 completed (SUMMARY + docs + state updated)
Stopped at: v1.7 phase 45 completed (SUMMARY + docs + state updated)
Next action: Start next milestone planning (/paul:milestone or /paul:plan)
Resume file: .paul/ROADMAP.md
---

View File

@@ -0,0 +1,290 @@
---
phase: 45-shoppro-status-push
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Modules/Settings/ShopproApiClient.php
- src/Modules/Settings/ShopproStatusSyncService.php
- src/Modules/Settings/ShopproOrderSyncStateRepository.php
- database/migrations/20260327_000071_add_last_status_pushed_at_to_sync_state.sql
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
---
<objective>
## Goal
Zaimplementowac synchronizacje statusow zamowien w kierunku orderPRO -> shopPRO. Gdy uzytkownik zmieni status zamowienia w orderPRO, cron pushuje te zmiane do shopPRO API.
## Purpose
Uzytkownik ustawil w konfiguracji integracji kierunek `orderpro_to_shoppro`, ale ten kierunek nie jest zaimplementowany — `ShopproStatusSyncService::sync()` jawnie go pomija (`$unsupportedCount++; continue`). Zamowienia zmienione w orderPRO nie aktualizuja sie w shopPRO.
## Output
- Metoda PUT w `ShopproApiClient` do aktualizacji statusu zamowienia w shopPRO
- Logika push w `ShopproStatusSyncService` dla kierunku `orderpro_to_shoppro`
- Migracja: kolumna `last_status_pushed_at` w `integration_order_sync_state`
- Aktualizacja dokumentacji
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@src/Modules/Settings/ShopproApiClient.php
@src/Modules/Settings/ShopproStatusSyncService.php
@src/Modules/Settings/ShopproOrderSyncStateRepository.php
@src/Modules/Settings/ShopproStatusMappingRepository.php
@src/Modules/Settings/ShopproIntegrationsRepository.php
@src/Modules/Cron/ShopproStatusSyncHandler.php
## shopPRO API Reference (z kodu shopPRO)
Endpoint: `PUT /api.php?endpoint=orders&action=change_status&id={orderId}`
Body JSON: `{"status_id": <int>, "send_email": <bool>}`
Auth: header `X-Api-Key`
Response: `{"status":"ok","data":{"order_id":<int>,"status_id":<int>,"changed":<bool>}}`
Statusy shopPRO: numeryczne ID z endpointu `GET /api.php?endpoint=dictionaries&action=statuses`
Tabela `order_status_mappings`: `shoppro_status_code` przechowuje numeryczne ID statusu shopPRO jako string.
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| sonar-scanner | required | Po APPLY, przed UNIFY | ○ |
</skills>
<acceptance_criteria>
## AC-1: API Client obsluguje PUT change_status
```gherkin
Given integracja shopPRO z poprawnym base_url i api_key
When wywolana zostanie metoda ShopproApiClient::updateOrderStatus()
Then wysylane jest zadanie PUT do /api.php?endpoint=orders&action=change_status&id={id}
And body zawiera JSON z status_id (int) i send_email (bool)
And zwracany jest wynik z polem ok, http_code, message, changed
```
## AC-2: Push kierunek dziala w cron sync
```gherkin
Given integracja shopPRO z direction = orderpro_to_shoppro
And zamowienie w orderPRO ma zmieniony status (wpis w order_status_history z change_source = manual)
And istnieje mapowanie orderpro_status_code -> shoppro_status_code w order_status_mappings
When uruchomi sie cron shoppro_order_status_sync
Then ShopproStatusSyncService pushuje nowy status do shopPRO API
And aktualizuje last_status_pushed_at w integration_order_sync_state
And nie pushuje zmian z change_source = import/sync (unikniecie petli)
```
## AC-3: Brak mapowania nie blokuje synca
```gherkin
Given zamowienie ma status orderpro bez mapowania na shoppro
When cron probuje pushowac status
Then pomija to zamowienie bez bledu
And kontynuuje przetwarzanie pozostalych
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Migracja DB + metoda PUT w ShopproApiClient</name>
<files>
database/migrations/20260327_000071_add_last_status_pushed_at_to_sync_state.sql,
src/Modules/Settings/ShopproApiClient.php,
src/Modules/Settings/ShopproOrderSyncStateRepository.php
</files>
<action>
1. Utworzyc migracje idempotentna (IF NOT EXISTS pattern):
```sql
ALTER TABLE integration_order_sync_state
ADD COLUMN IF NOT EXISTS last_status_pushed_at DATETIME NULL DEFAULT NULL
AFTER last_success_at;
```
2. W `ShopproApiClient` dodac publiczna metode `updateOrderStatus()`:
- Sygnatura: `updateOrderStatus(string $baseUrl, string $apiKey, int $timeoutSeconds, int $orderId, int $statusId, bool $sendEmail = false): array`
- Zwraca: `array{ok:bool, http_code:int|null, message:string, changed:bool}`
- URL: `$baseUrl/api.php?endpoint=orders&action=change_status&id=$orderId`
- Metoda HTTP: PUT (CURLOPT_CUSTOMREQUEST => 'PUT')
- Body: JSON `{"status_id": $statusId, "send_email": $sendEmail}`
- Header: Content-Type: application/json + X-Api-Key
- SSL: reuse logiki z `requestJson()` — wydzielic wspolna metode `buildCurlHandle()` LUB zduplikowac SSL opts (preferuj wydzielenie)
- Parsowanie odpowiedzi: sprawdzic `data.changed` z odpowiedzi
3. W `ShopproOrderSyncStateRepository`:
- Dodac metode `getLastStatusPushedAt(int $integrationId): ?string`
Query: `SELECT last_status_pushed_at FROM integration_order_sync_state WHERE integration_id = :id`
Defensywnie: sprawdzic czy kolumna istnieje (dodac do resolveColumns)
- Dodac metode `updateLastStatusPushedAt(int $integrationId, string $datetime): void`
Reuse istniejacego upsertState z nowym kluczem w columnMap
Avoid:
- Nie modyfikowac istniejacych metod requestJson() — dodac nowa prywatna metode do PUT
- Nie uzywac file_get_contents — tylko curl
</action>
<verify>
php -l src/Modules/Settings/ShopproApiClient.php
php -l src/Modules/Settings/ShopproOrderSyncStateRepository.php
grep -c "updateOrderStatus" src/Modules/Settings/ShopproApiClient.php (powinno byc >= 1)
grep -c "last_status_pushed_at" src/Modules/Settings/ShopproOrderSyncStateRepository.php (powinno byc >= 1)
</verify>
<done>AC-1 satisfied: ShopproApiClient ma metode PUT do zmiany statusu w shopPRO</done>
</task>
<task type="auto">
<name>Task 2: Implementacja push w ShopproStatusSyncService</name>
<files>
src/Modules/Settings/ShopproStatusSyncService.php
</files>
<action>
1. Dodac nowe zaleznosci w konstruktorze:
- `ShopproApiClient $apiClient`
- `ShopproOrderSyncStateRepository $syncState`
- `ShopproStatusMappingRepository $statusMappings`
- `PDO $pdo` (do query order_status_history + orders)
2. Zmodyfikowac `sync()` — zamiast `continue` dla `DIRECTION_ORDERPRO_TO_SHOPPRO`, wywolac nowa prywatna metode `syncPushDirection(int $integrationId, array $integration): array`
3. Implementacja `syncPushDirection()`:
a) Pobrac API credentials z integracji (reuse wzorca z istniejacego kodu):
- base_url, api_key (via $this->integrations->getApiKeyDecrypted), timeout
b) Zbudowac reverse status map:
- Pobrac mappingi z $this->statusMappings->listByIntegration($integrationId)
- Odwrocic: `$reverseMap[$orderpro_code] = $shoppro_code`
- Uwaga: jesli wiele shopPRO kodow mapuje na ten sam orderPRO kod, wziac pierwszy
c) Pobrac `last_status_pushed_at` z $this->syncState
d) Query do bazy — znalezc zamowienia z recznymi zmianami statusu po kursore:
```sql
SELECT DISTINCT o.id AS order_id, o.source_order_id, o.external_status_id,
MAX(h.changed_at) AS latest_change
FROM order_status_history h
JOIN orders o ON o.id = h.order_id
WHERE o.integration_id = :integration_id
AND h.change_source IN ('manual')
AND h.changed_at > :last_pushed_at -- lub brak warunku jesli null
GROUP BY o.id, o.source_order_id, o.external_status_id
ORDER BY latest_change ASC
LIMIT 50
```
Jesli `last_status_pushed_at` jest null, uzyc ostatnich 24h jako fallback.
e) Dla kazdego zamowienia:
- Pobrac aktualny `external_status_id` (orderpro status code)
- Sprawdzic reverse map: `$shopproStatusCode = $reverseMap[$orderproStatus] ?? null`
- Jesli brak mapowania — pominac (log do wyniku)
- Jesli jest — wywolac `$this->apiClient->updateOrderStatus($baseUrl, $apiKey, $timeout, (int)$sourceOrderId, (int)$shopproStatusCode)`
- Zapisac wynik (success/fail count)
- Zaktualizowac kursor `latest_change`
f) Po zakonczeniu petli: `$this->syncState->updateLastStatusPushedAt($integrationId, $latestChangeAt)`
g) Zwrocic wynik:
```php
['ok' => true, 'direction' => 'orderpro_to_shoppro', 'pushed' => $pushed, 'skipped' => $skipped, 'failed' => $failed]
```
4. Zaktualizowac wynik `sync()`:
- Zbierac wyniki z push integracji osobno
- Dodac do glownego wyniku
5. Zaktualizowac konstruktor w `CronHandlerFactory` jesli potrzebne nowe zaleznosci dla ShopproStatusSyncService
Avoid:
- Nie pushowac zmian z change_source = 'import' lub 'sync' — to spowodowaloby petle
- Nie modyfikowac logiki pull direction — zostawic ja nienaruszona
- Nie pushowac statusow bez mapowania — po cichu pominac
</action>
<verify>
php -l src/Modules/Settings/ShopproStatusSyncService.php
grep -c "syncPushDirection" src/Modules/Settings/ShopproStatusSyncService.php (powinno byc >= 2)
grep -c "DIRECTION_ORDERPRO_TO_SHOPPRO" src/Modules/Settings/ShopproStatusSyncService.php (powinno byc >= 2)
grep "unsupportedCount" src/Modules/Settings/ShopproStatusSyncService.php | head -3 (nie powinno byc juz w kontekscie orderpro_to_shoppro)
</verify>
<done>AC-2 i AC-3 satisfied: Push direction dziala w cron, brak mapowania nie blokuje</done>
</task>
<task type="auto">
<name>Task 3: Aktualizacja CronHandlerFactory + dokumentacja</name>
<files>
src/Modules/Cron/CronHandlerFactory.php,
DOCS/DB_SCHEMA.md,
DOCS/ARCHITECTURE.md,
DOCS/TECH_CHANGELOG.md
</files>
<action>
1. W `CronHandlerFactory` — zaktualizowac tworzenie `ShopproStatusSyncService`:
- Dodac brakujace zaleznosci do konstruktora (ShopproApiClient, ShopproOrderSyncStateRepository, ShopproStatusMappingRepository, PDO)
- Reuse istniejacych instancji jesli juz sa tworzone w factory
2. W `DOCS/DB_SCHEMA.md`:
- Dodac kolumne `last_status_pushed_at` do opisu `integration_order_sync_state`
3. W `DOCS/ARCHITECTURE.md`:
- Dodac opis metody `ShopproApiClient::updateOrderStatus()` (PUT)
- Dodac opis metody `ShopproStatusSyncService::syncPushDirection()` i logiki reverse mapping
- Zaktualizowac opis crona `shoppro_order_status_sync` — obsluguje oba kierunki
4. W `DOCS/TECH_CHANGELOG.md`:
- Dodac wpis chronologiczny: implementacja push direction orderpro_to_shoppro
Avoid:
- Nie modyfikowac istniejacych cron handlerow
- Nie zmieniac interwalu crona
</action>
<verify>
php -l src/Modules/Cron/CronHandlerFactory.php
grep "ShopproApiClient" src/Modules/Cron/CronHandlerFactory.php (powinno byc >= 1)
</verify>
<done>Dokumentacja i factory zaktualizowane</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- src/Modules/Orders/OrdersController.php (logika zmiany statusu pozostaje bez zmian)
- src/Modules/Orders/OrdersRepository.php (nie modyfikowac updateOrderStatus/recordStatusChange)
- src/Modules/Settings/ShopproOrdersSyncService.php (import/pull logika nienaruszona)
- src/Modules/Settings/ShopproOrderMapper.php (mapper importu)
- resources/views/settings/shoppro.php (UI konfiguracji integracji — nie wymaga zmian)
- Istniejace migracje
- Istniejace cron schedule/interval
## SCOPE LIMITS
- Tylko cron-based push (nie event-driven immediate push)
- Brak nowego UI — istniejacy dropdown direction juz obsluguje oba kierunki
- Nie implementowac synchronizacji platnosci w tym planie
- send_email w API shopPRO ustawic na false (nie wysylac maili klientom przy sync)
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `php -l` na wszystkich zmienionych plikach PHP (brak bledow skladni)
- [ ] Migracja SQL jest poprawna skladniowo
- [ ] `ShopproApiClient::updateOrderStatus()` wysyla PUT z poprawnym JSON body
- [ ] `ShopproStatusSyncService::sync()` obsluguje oba kierunki
- [ ] Reverse mapping poprawnie odwraca orderpro -> shoppro kody statusow
- [ ] Zmiany z `change_source = 'import'` NIE sa pushowane (brak petli)
- [ ] `CronHandlerFactory` poprawnie tworzy ShopproStatusSyncService z nowymi zaleznosciami
- [ ] Dokumentacja zaktualizowana (DB_SCHEMA, ARCHITECTURE, TECH_CHANGELOG)
</verification>
<success_criteria>
- Wszystkie taski ukonczone
- Wszystkie weryfikacje przeszly
- Brak nowych bledow skladni PHP
- Logika push nie interferuje z istniejaca logika pull
</success_criteria>
<output>
After completion, create `.paul/phases/45-shoppro-status-push/45-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,136 @@
---
phase: 45-shoppro-status-push
plan: 01
subsystem: integration
tags: [shoppro, status-sync, cron, curl-put, api]
requires:
- phase: shoppro-integrations
provides: ShopproApiClient, ShopproIntegrationsRepository, order_status_mappings, integration_order_sync_state
provides:
- Bidirectional status sync between orderPRO and shopPRO
- ShopproApiClient::updateOrderStatus() PUT method
- Reverse status mapping (orderpro -> shoppro)
affects: []
tech-stack:
added: []
patterns: [reverse-status-mapping, cursor-based-push-sync]
key-files:
created:
- database/migrations/20260327_000071_add_last_status_pushed_at_to_sync_state.sql
modified:
- src/Modules/Settings/ShopproApiClient.php
- src/Modules/Settings/ShopproStatusSyncService.php
- src/Modules/Settings/ShopproOrderSyncStateRepository.php
- src/Modules/Cron/CronHandlerFactory.php
key-decisions:
- "Refactor: wydzielenie executeRequest() z requestJson() dla reuse GET/PUT"
- "Push only change_source=manual to prevent sync loops"
- "Fallback 24h when last_status_pushed_at is null"
patterns-established:
- "Reverse mapping: orderpro_status_code -> shoppro_status_code (first match wins)"
- "Cursor-based push: last_status_pushed_at tracks sync progress per integration"
duration: 15min
started: 2026-03-27T00:00:00Z
completed: 2026-03-27T00:15:00Z
---
# Phase 45 Plan 01: ShopPRO Status Push Summary
**Implementacja synchronizacji statusow zamowien orderPRO -> shopPRO przez cron z reverse mapping i PUT API**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~15min |
| Tasks | 3 completed |
| Files modified | 7 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: API Client obsluguje PUT change_status | Pass | `updateOrderStatus()` wysyla PUT z JSON body `{status_id, send_email}` |
| AC-2: Push kierunek dziala w cron sync | Pass | `syncPushDirection()` query manual changes, reverse map, API call, cursor update |
| AC-3: Brak mapowania nie blokuje synca | Pass | Brak mapowania = `$skipped++`, kontynuacja petli |
## Accomplishments
- `ShopproApiClient::updateOrderStatus()` — metoda PUT do `/api.php?endpoint=orders&action=change_status`
- `ShopproStatusSyncService::syncPushDirection()` — pelna logika push: reverse mapping, query `order_status_history` (tylko `change_source=manual`), wywolanie API, aktualizacja kursora
- Refactor `ShopproApiClient` — wydzielenie `executeRequest()` z `requestJson()` dla reuse GET/PUT (eliminacja duplikacji SSL/curl logic)
- Kursor `last_status_pushed_at` w `integration_order_sync_state` z defensywnym sprawdzaniem istnienia kolumny
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `database/migrations/20260327_000071_*.sql` | Created | Migracja: kolumna `last_status_pushed_at` |
| `src/Modules/Settings/ShopproApiClient.php` | Modified | Nowa metoda `updateOrderStatus()` + refactor na `executeRequest()` |
| `src/Modules/Settings/ShopproStatusSyncService.php` | Modified | Implementacja push direction z reverse mapping |
| `src/Modules/Settings/ShopproOrderSyncStateRepository.php` | Modified | Metody `getLastStatusPushedAt()`, `updateLastStatusPushedAt()` |
| `src/Modules/Cron/CronHandlerFactory.php` | Modified | Nowe zaleznosci dla ShopproStatusSyncService, reuse instancji repo |
| `DOCS/DB_SCHEMA.md` | Modified | Dokumentacja kolumny `last_status_pushed_at` |
| `DOCS/ARCHITECTURE.md` | Modified | Opis push direction i nowej metody API |
| `DOCS/TECH_CHANGELOG.md` | Modified | Wpis chronologiczny |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Refactor `requestJson()` na `executeRequest()` | Eliminacja duplikacji SSL/curl logic miedzy GET i PUT | Latwiejsze dodawanie kolejnych metod HTTP |
| Push tylko `change_source=manual` | Zapobieganie petli synchronizacji (import->push->import) | Bezpieczna dwukierunkowa synchronizacja |
| Fallback 24h dla null cursor | Przy pierwszym uruchomieniu nie pushowac calej historii | Ograniczenie zakresu pierwszego synca |
| `send_email=false` w push | Sync nie powinien generowac maili do klientow | Brak niechcianych powiadomien |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 1 | Minimal |
**Total impact:** Essential refactor, no scope creep
### Auto-fixed Issues
**1. Refactor: duplikacja curl logic**
- **Found during:** Task 1
- **Issue:** Plan sugerowal zduplikowanie SSL opts lub wydzielenie `buildCurlHandle()`. Duplikacja bylaby nieoptymalna.
- **Fix:** Wydzielono `executeRequest()` z pelna logika curl (GET/PUT), `requestJson()` i `requestJsonPut()` sa teraz thin wrappers.
- **Files:** `ShopproApiClient.php`
- **Verification:** `php -l` pass
### Deferred Items
None
## Issues Encountered
None
## Next Phase Readiness
**Ready:**
- Push direction w pelni funkcjonalny — po uruchomieniu migracji i restarcie crona, statusy beda synchronizowane
- Istniejace mapowania statusow (skonfigurowane w UI) beda uzywane w obu kierunkach
**Concerns:**
- Migracja `000071` musi byc uruchomiona na serwerze produkcyjnym
- Nalezy zweryfikowac mapowania statusow w konfiguracji integracji shopPRO (czy pokrywaja wszystkie uzywane statusy)
**Blockers:**
None
---
*Phase: 45-shoppro-status-push, Plan: 01*
*Completed: 2026-03-27*