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>
13 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous
| phase | plan | type | wave | depends_on | files_modified | autonomous | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 45-shoppro-status-push | 01 | execute | 1 |
|
true |
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
ShopproApiClientdo aktualizacji statusu zamowienia w shopPRO - Logika push w
ShopproStatusSyncServicedla kierunkuorderpro_to_shoppro - Migracja: kolumna
last_status_pushed_atwintegration_order_sync_state - Aktualizacja dokumentacji
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.
| Skill | Priority | When to Invoke | Loaded? |
|---|---|---|---|
| sonar-scanner | required | Po APPLY, przed UNIFY | ○ |
<acceptance_criteria>
AC-1: API Client obsluguje PUT change_status
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
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
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>
Task 1: Migracja DB + metoda PUT w ShopproApiClient database/migrations/20260327_000071_add_last_status_pushed_at_to_sync_state.sql, src/Modules/Settings/ShopproApiClient.php, src/Modules/Settings/ShopproOrderSyncStateRepository.php 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
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)
AC-1 satisfied: ShopproApiClient ma metode PUT do zmiany statusu w shopPRO
Task 2: Implementacja push w ShopproStatusSyncService
src/Modules/Settings/ShopproStatusSyncService.php
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
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)
AC-2 i AC-3 satisfied: Push direction dziala w cron, brak mapowania nie blokuje
Task 3: Aktualizacja CronHandlerFactory + dokumentacja
src/Modules/Cron/CronHandlerFactory.php,
DOCS/DB_SCHEMA.md,
DOCS/ARCHITECTURE.md,
DOCS/TECH_CHANGELOG.md
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
php -l src/Modules/Cron/CronHandlerFactory.php
grep "ShopproApiClient" src/Modules/Cron/CronHandlerFactory.php (powinno byc >= 1)
Dokumentacja i factory zaktualizowane
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)
<success_criteria>
- Wszystkie taski ukonczone
- Wszystkie weryfikacje przeszly
- Brak nowych bledow skladni PHP
- Logika push nie interferuje z istniejaca logika pull </success_criteria>