diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md
index 1021325..f9c8a8c 100644
--- a/.paul/PROJECT.md
+++ b/.paul/PROJECT.md
@@ -12,9 +12,9 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
| Attribute | Value |
|-----------|-------|
-| Version | 3.4.0 |
-| Status | v3.4 shipped - Statistics Summary complete |
-| Last Updated | 2026-04-28 |
+| Version | 3.5.0 |
+| Status | v3.5 shipped - Payment Transition Event hotfix complete |
+| Last Updated | 2026-05-05 |
## Requirements
@@ -114,6 +114,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
- [x] Delivery Status Management: tabela `delivery_statuses` z CRUD panelem `/settings/delivery-statuses`, `DeliveryStatus::setRepository()` z DB fallbackiem, integracja DB-driven w dropdownach automatyzacji (warunek shipment_status + akcja update_shipment_status), osobna podstrona formularza CRUD (BREAKING: drop backward compat dla starych grupowych kluczy automatyzacji) — Phase 108
- [x] Checkbox dropdown multi-select filters: `/statistics/orders` korzysta z progresywnie ulepszanych selectow multiple z checkboxami, opcja "Wszystkie" i zachowanym kontraktem GET — Phase 109
- [x] Podsumowanie statystyk: `Statystyki -> Podsumowanie` z miesiecznymi wykresami liczby i wartosci zamowien per integracja plus `Razem`, Chart.js i fallback tabelaryczny — Phase 110
+- [x] Re-import zamowienia (Allegro + shopPRO) wykrywa tranzycje payment_status 0/1->2 i emituje `payment.status_changed` (chain reguly #7 zmienia status na `w_realizacji`); naprawa luki dla zamowien zaimportowanych przed potwierdzeniem platnosci (case #864) + backfill CLI — Phase 111
### Deferred
diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md
index fdaf8be..398c1a6 100644
--- a/.paul/ROADMAP.md
+++ b/.paul/ROADMAP.md
@@ -6,7 +6,7 @@ orderPRO to narzedzie do wielokanalowego zarzadzania sprzedaza. Projekt przechod
## Current Milestone
-Brak aktywnego milestone - v3.4 zamkniety. Nastepny milestone do zaplanowania.
+Brak aktywnego milestone - v3.5 zamkniety. Nastepny milestone do zaplanowania.
## Next Milestone
@@ -19,6 +19,19 @@ Kandydaci w kolejce:
## Completed Milestones
+
+v3.5 Payment Transition Event - 2026-05-05 (1 phase, 1 plan)
+
+Naprawa luki w re-imporcie zamowien Allegro/shopPRO: po potwierdzeniu platnosci re-import emituje `payment.status_changed`, co przez chain reguly #7 zmienia status na `w_realizacji`. Eliminuje przypadki zamowien zaimportowanych przed potwierdzeniem platnosci utykajacych w `nieoplacone` (case #864).
+
+| Phase | Name | Plans | Status |
+|-------|------|-------|--------|
+| 111 | Payment Transition Event | 1/1 | Complete |
+
+Archive: `.paul/phases/111-payment-transition-event/`
+
+
+
v3.4 Statistics Summary - 2026-04-28 (1 phase, 1 plan)
diff --git a/.paul/STATE.md b/.paul/STATE.md
index b912483..0bddf35 100644
--- a/.paul/STATE.md
+++ b/.paul/STATE.md
@@ -9,34 +9,33 @@ See: .paul/PROJECT.md (updated 2026-04-28)
## Current Position
-Milestone: v3.4 Statistics Summary - COMPLETE
-Phase: 110 of 110 - COMPLETE
-Plan: 110-01 - COMPLETE
-Version: 3.4.0
-Status: v3.4 shipped - gotowy do nastepnego milestone
-
-Last activity: 2026-04-28 - UNIFY Phase 110 / v3.4 milestone complete
+Milestone: v3.5 Payment Transition Event (hotfix) — COMPLETE
+Phase: 111 of 111 (Payment Transition Event) — COMPLETE
+Plan: 111-01 — COMPLETE
+Status: v3.5 shipped, awaiting transition (commit) i nastepny milestone
+Last activity: 2026-05-05 — UNIFY Phase 111 / Plan 111-01 complete
Progress:
-- Milestone v3.4: [##########] 100% (1/1 phases, 1/1 plans)
+- Milestone v3.5: [##########] 100% (1/1 phases, 1/1 plans)
+- Phase 111: [##########] 100%
## Loop Position
Current loop state:
```
-v3.4 milestone:
- Phase 110 (Statistics Summary):
- Plan 110-01: PLAN done APPLY done UNIFY done
- -> Phase 110 closed
--> v3.4 milestone closed
+v3.5 milestone:
+ Phase 111 (Payment Transition Event):
+ Plan 111-01: PLAN done APPLY done UNIFY done
+ -> Phase 111 closed
+-> v3.5 milestone closed (pending transition commit)
```
## Session Continuity
-Last session: 2026-04-28
-Stopped at: v3.4 milestone closed
-Next action: /paul:milestone - wybor i zaplanowanie nastepnego milestone
-Resume file: .paul/phases/110-statistics-summary/110-01-SUMMARY.md
+Last session: 2026-05-05
+Stopped at: v3.5 milestone closed
+Next action: transition-phase (PROJECT/ROADMAP update + git commit), nastepnie /paul:milestone
+Resume file: .paul/phases/111-payment-transition-event/111-01-SUMMARY.md
## Git State
diff --git a/.paul/changelog/2026-05-05.md b/.paul/changelog/2026-05-05.md
new file mode 100644
index 0000000..cb2a3de
--- /dev/null
+++ b/.paul/changelog/2026-05-05.md
@@ -0,0 +1,21 @@
+# 2026-05-05
+
+## Co zrobiono
+
+- [Phase 111, Plan 01] Payment Transition Event — re-import zamowienia (Allegro + shopPRO) wykrywa tranzycje payment_status 0/1 -> 2 i emituje `payment.status_changed`, dzieki czemu chain reguly automatyzacji #7 zmienia status na `w_realizacji`
+- OrderImportRepository: rozdzielenie `paymentTransition` (event, 0/1->2) i `statusOverwriteAllowed` (preservacja status_code z Phase 62)
+- AllegroOrderImportService + ShopproOrdersSyncService: emit `payment.status_changed` na re-imporcie (gate `!$wasCreated && $wasPaymentTransition`)
+- bin/backfill_payment_transition_111.php: jednorazowy CLI (no-op na obecnym stanie DB; zostaje jako safety net)
+- Aktualizacja .paul/codebase/architecture.md (Order Lifecycle pkt 2) i tech_changelog.md
+- Naprawa luki znalezionej w analizie zamowienia #864 (zaimportowane przed potwierdzeniem platnosci, utknelo w `nieoplacone`)
+
+## Zmienione pliki
+
+- `src/Modules/Orders/OrderImportRepository.php`
+- `src/Modules/Settings/AllegroOrderImportService.php`
+- `src/Modules/Settings/ShopproOrdersSyncService.php`
+- `bin/backfill_payment_transition_111.php`
+- `.paul/codebase/architecture.md`
+- `.paul/codebase/tech_changelog.md`
+- `.paul/phases/111-payment-transition-event/111-01-PLAN.md`
+- `.paul/phases/111-payment-transition-event/111-01-SUMMARY.md`
diff --git a/.paul/codebase/architecture.md b/.paul/codebase/architecture.md
index 35b9e54..c559f59 100644
--- a/.paul/codebase/architecture.md
+++ b/.paul/codebase/architecture.md
@@ -65,8 +65,9 @@ HTTP Request
### Order Lifecycle
1. **Import** — Cron handler → API client → `OrderImportService` → `OrdersRepository::insertOrder()` → `AutomationService::executeForNewOrder()`
-2. **Status update** — `OrdersController::updateStatus()` → `OrdersRepository::updateStatus()` → automation check
-3. **Status sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` → carrier API
+2. **Re-import (Phase 111)** — `OrderImportRepository::upsertOrderAggregate` wykrywa tranzycje `payment_status` z 0/1 na 2 i zwraca `payment_transition=true`. `AllegroOrderImportService` i `ShopproOrdersSyncService` na tej fladze emituja `payment.status_changed`, co przez chain reguly automatyzacji #7 zmienia `status_code` na `w_realizacji`. Logika preservacji `status_code` z Phase 62 pozostaje rozdzielona (`statusOverwriteAllowed` = `currentStatus='nieoplacone' && newPaymentStatus===2`).
+3. **Status update** — `OrdersController::updateStatus()` → `OrdersRepository::updateStatus()` → automation check
+4. **Status sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` → carrier API
### Statistics Summary
1. **Request** — `/statistics/summary` → `OrdersStatisticsController::summary()`
diff --git a/.paul/codebase/tech_changelog.md b/.paul/codebase/tech_changelog.md
index a9c5a13..8de46a5 100644
--- a/.paul/codebase/tech_changelog.md
+++ b/.paul/codebase/tech_changelog.md
@@ -1,5 +1,20 @@
# Technical Changelog
+## 2026-05-05 - Phase 111 Plan 01: Payment Transition Event
+
+**Co zrobiono:**
+- `OrderImportRepository::upsertOrderAggregate` - rozszerzona detekcja `payment_transition`. Teraz porownuje poprzedni `payment_status` z nowym (warunek `0/1 -> 2`) zamiast polegac wylacznie na `status_code='nieoplacone'`. Logika preservacji status_code z Phase 62 (`statusOverwriteAllowed`) zostala wydzielona jako osobna decyzja.
+- `OrderImportRepository::getCurrentStatusAndPaymentStatus()` - nowa metoda pomocnicza zastepujaca `getCurrentStatus()`, zwraca i status_code, i payment_status w jednym SELECT.
+- `AllegroOrderImportService::importSingleOrder` - dodaje emit `payment.status_changed` gdy `payment_transition && !$wasCreated`.
+- `ShopproOrdersSyncService::importOneOrder` - analogiczny emit `payment.status_changed`.
+- `bin/backfill_payment_transition_111.php` - jednorazowy CLI dla zamowien z `payment_status=2 && status_code='nieoplacone'` (allegro + shoppro), idempotentny, wzorzec z Phase 98.
+
+**Dlaczego:**
+- Zamowienie #864 (Allegro) zaimportowane 10s po zlozeniu, gdy Allegro jeszcze nie potwierdzilo platnosci. Re-import 2 minuty pozniej zaktualizowal payment_status na 2, ale `order.imported` jest gated przez `$wasCreated` (Phase 98), wiec automatyzacja "Zmien status na w realizacji (allegro)" nigdy nie odpalila.
+- Allegro nie mial odpowiednika `ShopproPaymentStatusSyncService`, wiec tranzycja platnosci znikala cicho. ShopPRO mial analogiczna luke w `ShopproOrdersSyncService` (flaga `payment_transition` byla wykrywana, ale nie emitowala eventu).
+- Regula automatyzacji #7 (`payment.status_changed` -> `update_order_status` na `w_realizacji`) nie ma warunku integration_id, wiec po wyemitowaniu eventu obejmie zarowno Allegro jak i shopPRO.
+- Idempotencja zalatwiona przez logike repo: po pierwszej tranzycji DB ma `payment_status=2`, kolejny re-import widzi old=2/new=2 i `payment_transition=false`. Brak duplikatow eventow.
+
## 2026-04-28 - Phase 110 Plan 01: Statistics Summary
**Co zrobiono:**
diff --git a/.paul/phases/111-payment-transition-event/111-01-PLAN.md b/.paul/phases/111-payment-transition-event/111-01-PLAN.md
new file mode 100644
index 0000000..c0caa11
--- /dev/null
+++ b/.paul/phases/111-payment-transition-event/111-01-PLAN.md
@@ -0,0 +1,318 @@
+---
+phase: 111-payment-transition-event
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - src/Modules/Orders/OrderImportRepository.php
+ - src/Modules/Settings/AllegroOrderImportService.php
+ - src/Modules/Settings/ShopproOrdersSyncService.php
+ - bin/backfill_payment_transition_111.php
+ - .paul/codebase/architecture.md
+ - .paul/codebase/tech_changelog.md
+autonomous: true
+delegation: off
+---
+
+
+## Goal
+Re-import zamowienia (Allegro + shopPRO) wykrywa tranzycje platnosci 0/1 → 2 i emituje event `payment.status_changed`, dzieki czemu chain regul automatyzacji (regula #7 → akcja `update_order_status` na `w_realizacji`) odpala sie dla zamowien zaimportowanych przed potwierdzeniem platnosci. Backfill naprawia istniejace zamowienia, ktore utknely w `nieoplacone` mimo `payment_status=2`.
+
+## Purpose
+Naprawa luki obserwowanej dla zamowienia #864 (Allegro): zamowienie zaimportowane 10s po zlozeniu, gdy Allegro jeszcze nie potwierdzilo platnosci (payment_status=0). Re-import 2 minuty pozniej zaktualizowal payment_status do 2, ale `order.imported` jest gated przez `$wasCreated` (commit 7eefd1a, Phase 98), wiec automatyzacja "Zmien status na w realizacji (allegro)" nigdy nie odpalila. Allegro nie ma odpowiednika `ShopproPaymentStatusSyncService`, wiec tranzycja platnosci znika cicho. ShopPRO ma identyczna luke w `ShopproOrdersSyncService` (flaga `payment_transition` jest wykrywana w repo, ale nie emituje eventu).
+
+## Output
+- `OrderImportRepository::upsertOrderAggregate` zwraca `payment_transition=true` gdy poprzedni `payment_status` byl 0 lub 1 i nowy to 2 (rozszerzenie istniejacej logiki opartej tylko o `status_code='nieoplacone'`)
+- AllegroOrderImportService i ShopproOrdersSyncService emituja `automationService->trigger('payment.status_changed', ...)` na `payment_transition`
+- CLI `bin/backfill_payment_transition_111.php` naprawia istniejace zamowienia z `payment_status=2 && status_code='nieoplacone'`
+- Aktualizacja `.paul/codebase/architecture.md` (sekcja Order Lifecycle) i `.paul/codebase/tech_changelog.md`
+
+
+
+
+- **Zakres** — Czy rozszerzyc tez na ShopproOrdersSyncService?
+ → Odpowiedz: Allegro + shopPRO — spojnie obie sciezki re-importu emituja event. ShopproPaymentStatusSyncService osobno tez emituje, ale idempotencja (rule 7 jest idempotentna przy `update_order_status` — repeat call do tego samego status_code nie daje nowego status_history wpisu z `change_source='manual'` jesli status sie nie zmienia) zabezpiecza przed efektami duplikatu.
+- **Tranzycja** — Ktory przypadek wyzwala `payment.status_changed`?
+ → Odpowiedz: Tylko 0/1 → 2. Wymaga rozszerzenia istniejacej logiki w OrderImportRepository (obecnie sprawdza `currentStatus='nieoplacone' && newPaymentStatus===2`) o porownanie poprzedniego payment_status.
+- **Backfill** — Naprawic istniejace zamowienia?
+ → Odpowiedz: Tak, CLI script wzorem `bin/backfill_shipped_status_98.php` (Phase 98). Idempotentny, wzor: znajdz `payment_status=2 && status_code='nieoplacone'`, wymus update przez OrdersRepository::updateOrderStatus.
+- **Idempotencja** — Ochrona przed wielokrotnym emitowaniem?
+ → Odpowiedz: Wystarcza istniejaca logika repo. Po pierwszej tranzycji status_code sie zmienia (przez akcje regul 7), wiec kolejny re-import ma `currentStatus='w_realizacji'` i `paymentTransition=false`. Nowa logika 0/1→2 oparta o porownanie payment_status tez bedzie self-resetting: po pierwszej tranzycji DB ma payment_status=2, kolejny re-import widzi old=2 i nowy=2, transition=false.
+
+
+## Project Context
+@.paul/PROJECT.md
+@.paul/STATE.md
+@.paul/ROADMAP.md
+
+## Codebase Refs
+@.paul/codebase/architecture.md
+@.paul/codebase/db_schema.md
+
+## Source Files
+@src/Modules/Orders/OrderImportRepository.php
+@src/Modules/Settings/AllegroOrderImportService.php
+@src/Modules/Settings/ShopproOrdersSyncService.php
+@src/Modules/Automation/AutomationService.php
+@bin/backfill_shipped_status_98.php
+
+## Reference: Existing Pattern
+- ShopproPaymentStatusSyncService.php:256 — `$this->automation?->trigger('payment.status_changed', $orderId, [...])` — wzor invocation
+- OrderImportRepository.php:41-50 — istniejaca logika `paymentTransition` (status-based, do rozszerzenia)
+- bin/backfill_shipped_status_98.php — wzor jednorazowego CLI backfillu
+- Regula #7 w DB (sprawdzona, payment_status=2, akcja update_order_status → w_realizacji, BEZ warunku integration_id) — odpali sie dla Allegro automatycznie po wyemitowaniu eventu
+
+
+
+
+## AC-1: Repo wykrywa tranzycje 0/1 → 2
+```gherkin
+Given zamowienie istnieje w DB z payment_status=0 (lub 1) i status_code='nieoplacone'
+When AllegroOrderImportService::importSingleOrder lub ShopproOrdersSyncService re-importuje to zamowienie z payment_status=2 w aggregate
+Then OrderImportRepository::upsertOrderAggregate zwraca `payment_transition=true`
+And status_code w bazie zostaje zaktualizowany z payloadu (nie zachowany jako nieoplacone)
+
+Given zamowienie istnieje w DB z payment_status=2
+When re-import wykona aggregate z payment_status=2
+Then `payment_transition=false` (brak duplikatu eventu przy kolejnych re-importach)
+
+Given zamowienie istnieje w DB z payment_status=0 i status_code='anulowane' (lub inny niz nieoplacone)
+When re-import wykona aggregate z payment_status=2
+Then `payment_transition=true` (wykrywanie wg payment_status, nie status_code)
+And status_code zostaje chroniony zgodnie z istniejaca logika preservacji (Phase 62) — pole status_code nie jest nadpisywane jesli currentStatus != 'nieoplacone'
+```
+
+## AC-2: Allegro i shopPRO re-import emituje payment.status_changed
+```gherkin
+Given OrderImportRepository zwrocilo `payment_transition=true` z `created=false`
+When AllegroOrderImportService::importSingleOrder lub ShopproOrdersSyncService::importOneOrder kontynuuje
+Then automationService->trigger('payment.status_changed', $orderId, $context) zostaje wywolane
+And $context zawiera: integration_id, source ('allegro' lub 'shoppro'), new_payment_status='2', old_payment_status (numeric string z DB)
+And automation_execution_logs ma wpis dla rule_id=7 (Zmien status na oplacone) ze status='success'
+And status_code zamowienia w DB zmienia sie z 'nieoplacone' na 'w_realizacji' (przez chain reguly 7)
+```
+
+## AC-3: Backfill naprawia historyczne dane
+```gherkin
+Given w DB istnieja zamowienia z payment_status=2 AND LOWER(status_code)='nieoplacone' AND source IN ('allegro', 'shoppro')
+When operator uruchamia `php bin/backfill_payment_transition_111.php`
+Then kazde takie zamowienie dostaje update status_code = 'w_realizacji' przez OrdersRepository::updateOrderStatus
+And order_status_history dostaje wpis nieoplacone -> w_realizacji z change_source='import' i actor='Backfill 111'
+And skrypt loguje na stdout liste zamowien (id, source, integration_id, internal_order_number)
+And ponowne uruchomienie skryptu jest no-opem (idempotencja: po pierwszym przebiegu zadne zamowienie nie spelnia warunku)
+```
+
+## AC-4: Brak regresji w istniejacym flow
+```gherkin
+Given pierwszy import zamowienia (created=true) z payment_status=2
+When AllegroOrderImportService::importSingleOrder konczy
+Then `order.imported` jest emitowane jak dotychczas (gate $wasCreated)
+And `payment.status_changed` NIE jest emitowane przy created=true (transition liczy sie tylko przy update)
+
+Given zamowienie shopPRO juz oplacone w DB (payment_status=2)
+When osobny ShopproPaymentStatusSyncService cron wykryje brak zmian
+Then nowy emit z importu nie powiela starego — istniejaca regula 7 idempotentnie zostawia status w_realizacji bez zmian (status_code juz jest w_realizacji, brak nowego status_history wpisu)
+```
+
+
+
+
+
+
+ Task 1: Rozszerz OrderImportRepository o detekcje tranzycji 0/1 → 2
+ src/Modules/Orders/OrderImportRepository.php
+
+ 1. W `upsertOrderAggregate` (linie 33-81), w bloku `if (!$created)`:
+ - Zamiast `getCurrentStatus($existingOrderId)`, wprowadz `getCurrentStatusAndPaymentStatus(int $orderId): array{status_code:string, payment_status:int}` ktora jednym zapytaniem czyta oba pola.
+ - Wylicz `$paymentTransition = in_array($oldPaymentStatus, [0, 1], true) && $newPaymentStatus === 2;`
+ - Zachowaj istniejaca logike preservacji status_code: `$statusOverwriteAllowed = ($currentStatus === 'nieoplacone' && $newPaymentStatus === 2);` — to (a nie paymentTransition) decyduje czy `$orderData['status_code']` zostaje nadpisany (Phase 62).
+ - W `replacePayments`/`replaceShipments`/`replaceStatusHistory` warunek bedzie teraz `$paymentTransition || $statusOverwriteAllowed` — utrzymujemy obecne zachowanie shopPRO (replacePayments przy paymentTransition).
+ 2. Wartosc zwracana: zachowaj `[order_id, created, payment_transition]` ale teraz `payment_transition` ma szersza semantyke (0/1→2 niezalznie od status_code).
+ 3. Dodaj `private function getCurrentStatusAndPaymentStatus(int $orderId): array`. Stary `getCurrentStatus()` mozna usunac jesli nie uzywany w innym miejscu (sprawdzic grep).
+
+ Uwaga:
+ - NIE zmieniaj logiki `replaceStatusHistory($orderId, ...)` — historia tworzona tylko przy `$created` (zachowane w nowym warunku przy `$statusOverwriteAllowed`). Jesli payment_transition NIE rowna sie statusOverwriteAllowed, paymetTransition NIE wymusza nowego wpisu historii (chain reguly 7 wpisze go przez OrdersRepository::updateOrderStatus).
+
+
+ - `php -l src/Modules/Orders/OrderImportRepository.php` (lint OK)
+ - Manualny test SQL: `SELECT payment_status, status_code FROM orders WHERE id = 864;` przed re-importem testowego (lokalna kopia) → 0/nieoplacone, po wymuszonym re-imporcie powinno byc 2/(zachowany lub w_realizacji).
+ - Brak zmian w testach jednostkowych jesli nie istnieja dla tego repo (sprawdzic `tests/Unit/`).
+
+ AC-1 spelnione: payment_transition oparte o porownanie payment_status, status_code chroniony zgodnie z dotychczasowa logika preservacji.
+
+
+
+ Task 2: Emit payment.status_changed w Allegro + shopPRO import services
+ src/Modules/Settings/AllegroOrderImportService.php, src/Modules/Settings/ShopproOrdersSyncService.php
+
+ AllegroOrderImportService.php (po linii 99-106, po istniejacym blocku `order.imported`):
+ ```php
+ $wasPaymentTransition = !empty($saveResult['payment_transition']);
+ if (!$wasCreated && $wasPaymentTransition && $this->automationService !== null) {
+ $this->automationService->trigger('payment.status_changed', $savedOrderId, [
+ 'source' => IntegrationSources::ALLEGRO,
+ 'integration_id' => (int) ($mapped['order']['integration_id'] ?? 0),
+ 'old_payment_status' => '', // nieznane na poziomie service; repo nie eksponuje, nie kluczowe dla rule 7 (sprawdza tylko new_payment_status)
+ 'new_payment_status' => (string) ($mapped['order']['payment_status'] ?? ''),
+ ]);
+ }
+ ```
+
+ ShopproOrdersSyncService.php (po linii 273-280, po istniejacym blocku `order.imported`):
+ ```php
+ $wasPaymentTransition = !empty($save['payment_transition']);
+ if (!$wasCreated && $wasPaymentTransition && $this->automationService !== null) {
+ $this->automationService->trigger('payment.status_changed', $savedOrderId, [
+ 'source' => 'shoppro',
+ 'integration_id' => $integrationId,
+ 'old_payment_status' => '',
+ 'new_payment_status' => (string) ($aggregate['order']['payment_status'] ?? ''),
+ ]);
+ }
+ ```
+
+ Uwagi:
+ - `order.imported` musi pozostac gated przez `$wasCreated` — NIE zmieniaj tego (commit 7eefd1a).
+ - Emit `payment.status_changed` jest wykonywany TYLKO gdy `!$wasCreated` (re-import) — przy pierwszym imporcie payment_transition jest false (created=true → nie wchodzi do bloku detekcji).
+ - Sciezka `status_sync` (AllegroStatusSyncService → importSingleOrder('status_sync')) korzysta z tej samej metody, wiec automatycznie skorzysta z nowego emit.
+ - Reuse: dolacz brakujacy use `App\Core\Constants\IntegrationSources` jesli jeszcze nie ma (sprawdzic).
+
+
+ - `php -l src/Modules/Settings/AllegroOrderImportService.php`
+ - `php -l src/Modules/Settings/ShopproOrdersSyncService.php`
+ - Lokalny test: rezstart importu zamowienia testowego z payment_status=0 → ustaw payment.status w Allegro → kolejny status_sync → sprawdz `automation_execution_logs WHERE event_type='payment.status_changed' AND order_id=...` (powinien byc wpis dla rule_id=7) i `orders.status_code` powinno byc 'w_realizacji'.
+
+ AC-2 spelnione: oba serwisy importu emituja `payment.status_changed` na payment_transition. AC-4 zachowane: order.imported nadal gated.
+
+
+
+ Task 3: Backfill CLI dla istniejacych zamowien z luka platnosci
+ bin/backfill_payment_transition_111.php
+
+ Stworz nowy plik wzorem `bin/backfill_shipped_status_98.php`:
+
+ ```php
+ pdo();
+ $orders = $app->orders();
+
+ $statement = $pdo->prepare(
+ "SELECT id, internal_order_number, source, integration_id
+ FROM orders
+ WHERE source IN ('allegro', 'shoppro')
+ AND payment_status = 2
+ AND LOWER(COALESCE(status_code, '')) = 'nieoplacone'"
+ );
+ $statement->execute();
+ $rows = $statement->fetchAll(PDO::FETCH_ASSOC) ?: [];
+
+ if ($rows === []) {
+ echo "[backfill-111] Brak zamowien do naprawy.\n";
+ exit(0);
+ }
+
+ echo "[backfill-111] Znaleziono " . count($rows) . " zamowien:\n";
+ foreach ($rows as $row) {
+ $orderId = (int) $row['id'];
+ $internalNumber = (string) $row['internal_order_number'];
+ $source = (string) $row['source'];
+ $integrationId = (int) $row['integration_id'];
+
+ $updated = $orders->updateOrderStatus(
+ $orderId,
+ 'w_realizacji',
+ 'import',
+ 'Backfill 111'
+ );
+
+ $marker = $updated ? 'OK' : 'SKIP';
+ echo sprintf(" [%s] #%d %s (source=%s, integration=%d)\n", $marker, $orderId, $internalNumber, $source, $integrationId);
+ }
+
+ echo "[backfill-111] Zakonczono.\n";
+ ```
+
+ Wymagania:
+ - Idempotentny: po updateOrderStatus zamowienie ma status_code='w_realizacji', wiec ponowne uruchomienie nie znajdzie nic.
+ - Bez recznego SQL — uzywaj OrdersRepository::updateOrderStatus (tworzy wpis w order_status_history + activity log).
+ - Bez parametrow CLI — operator uruchamia raz po deployu.
+ - Sprawdzic `bin/backfill_shipped_status_98.php` przed pisaniem — uzyc dokladnie tego samego patternu inicjalizacji (jak app jest dostepny: `require app.php` raz; jezeli phase 98 ma inny pattern, dopasowac).
+
+
+ - `php -l bin/backfill_payment_transition_111.php`
+ - DRY-RUN przez SELECT preview: `SELECT id, internal_order_number, source, status_code, payment_status FROM orders WHERE source IN ('allegro','shoppro') AND payment_status=2 AND LOWER(COALESCE(status_code,''))='nieoplacone';`
+ - Uruchomienie: `php bin/backfill_payment_transition_111.php` → log z liczba znalezionych + per-zamowienie [OK]
+ - Drugi run: `[backfill-111] Brak zamowien do naprawy.` (idempotencja)
+ - Spot-check w DB: `SELECT id, status_code FROM orders WHERE id IN (...)` → wszystkie `w_realizacji`
+ - `SELECT * FROM order_status_history WHERE order_id IN (...) ORDER BY id DESC LIMIT 5` — powinny byc wpisy `nieoplacone → w_realizacji` z change_source='import'
+
+ AC-3 spelnione: backfill naprawia historyczne dane idempotentnie.
+
+
+
+ Task 4: Aktualizacja dokumentacji codebase (architecture + tech_changelog)
+ .paul/codebase/architecture.md, .paul/codebase/tech_changelog.md
+
+ `.paul/codebase/architecture.md`:
+ - W sekcji "Order Lifecycle" rozszerz pkt 1 (Import) o: "Re-import: jezeli payment_status zmienia sie z 0/1 na 2, OrderImportRepository zwraca `payment_transition=true`, a service importu (AllegroOrderImportService, ShopproOrdersSyncService) emituje `payment.status_changed`. Chain regul automatyzacji (regula #7) wykonuje update_order_status → w_realizacji."
+ - Dodaj w "Key Decisions" (lub w odpowiedniej sekcji) krotki bullet o phase 111.
+
+ `.paul/codebase/tech_changelog.md`:
+ - Dodaj wpis na gore (chronologicznie): `## 2026-05-05 — Phase 111: Payment Transition Event` z opisem co i dlaczego (zamowienie #864 case).
+
+ PROJECT.md (`Validated (Shipped)` lista) — UNIFY phase doda wpis po zakonczeniu, NIE w PLAN/APPLY.
+
+
+ Manualny przeglad obu plikow — sekcje istnieja, formatowanie spojne z reszta.
+
+ Dokumentacja codebase zsynchronizowana z kodem.
+
+
+
+
+
+
+## DO NOT CHANGE
+- `AllegroOrderImportService.php:99` gate `if ($wasCreated && ...)` dla `order.imported` — Phase 98 decyzja
+- `ShopproPaymentStatusSyncService.php` — istniejacy emit `payment.status_changed` zostaje (idempotentnie wspolistnieje)
+- Regula automatyzacji #7 w DB — nie modyfikujemy warunkow ani akcji (akceptujemy ze obejmuje wszystkie integracje)
+- Logika preservacji status_code w OrderImportRepository (Phase 62) — `$statusOverwriteAllowed` musi zachowac semantyke `currentStatus='nieoplacone' && newPaymentStatus===2`
+- `database/migrations/*` — bez nowych migracji (idempotencja zalatwiona logika kodu, nie schema)
+
+## SCOPE LIMITS
+- Bez zmian w `AutomationService::evaluateOrderStatusCondition` ani innych warunkach automatyzacji
+- Bez modyfikacji UI panelu automatyzacji
+- Bez nowych pol w tabeli `orders` (idempotencja przez logike, nie przez flage w DB)
+- Bez zmian w `AllegroStatusSyncService` (korzysta z importSingleOrder, wiec dziedziczy fix automatycznie)
+- Bez testow jednostkowych dla nowej logiki (na uzytkownika decyzja czy dopisywac w UNIFY; tests/ nie ma obecnie pokrycia OrderImportRepository)
+
+
+
+
+Przed zamknieciem planu:
+- [ ] `php -l` przechodzi dla wszystkich zmienionych plikow
+- [ ] AC-1: zamowienie testowe Allegro z payment_status=0 + re-import z payment_status=2 → `automation_execution_logs` ma wpis rule_id=7
+- [ ] AC-2: status_code zamowienia testowego zmienia sie 'nieoplacone' → 'w_realizacji'
+- [ ] AC-3: backfill znajduje + naprawia istniejace zamowienia (run #1 znajduje, run #2 = no-op)
+- [ ] AC-4: pierwszy import (created=true) NIE emituje `payment.status_changed`
+- [ ] Dokumentacja `.paul/codebase/architecture.md` + `.paul/codebase/tech_changelog.md` zaktualizowana
+
+
+
+- Wszystkie 4 zadania ukonczone
+- Wszystkie 4 AC zweryfikowane manualnie na lokalnej kopii DB lub na zamowieniu testowym
+- `php -l` lint czysty
+- Brak nowych warningow PHP w logach po deploy
+
+
+
diff --git a/.paul/phases/111-payment-transition-event/111-01-SUMMARY.md b/.paul/phases/111-payment-transition-event/111-01-SUMMARY.md
new file mode 100644
index 0000000..757d4ba
--- /dev/null
+++ b/.paul/phases/111-payment-transition-event/111-01-SUMMARY.md
@@ -0,0 +1,151 @@
+---
+phase: 111-payment-transition-event
+plan: 01
+subsystem: automation
+tags: [allegro, shoppro, payment-status, automation, import]
+
+requires:
+ - phase: 98-order-imported-first-only
+ provides: gate $wasCreated dla order.imported (commit 7eefd1a)
+ - phase: 62-import-reimport-safety
+ provides: logika preservacji status_code przy re-imporcie
+provides:
+ - detekcja payment_transition oparta o porownanie payment_status (0/1 -> 2)
+ - emit payment.status_changed w Allegro/shopPRO re-imporcie
+ - bin/backfill_payment_transition_111.php (idempotent CLI)
+affects: [Allegro re-import flow, ShopPRO re-import flow, Automation rule chain]
+
+tech-stack:
+ added: []
+ patterns:
+ - "Rozdzielenie paymentTransition (event) i statusOverwriteAllowed (status_code preservation) jako osobnych decyzji w upsertOrderAggregate"
+
+key-files:
+ created:
+ - bin/backfill_payment_transition_111.php
+ modified:
+ - src/Modules/Orders/OrderImportRepository.php
+ - src/Modules/Settings/AllegroOrderImportService.php
+ - src/Modules/Settings/ShopproOrdersSyncService.php
+ - .paul/codebase/architecture.md
+ - .paul/codebase/tech_changelog.md
+
+key-decisions:
+ - "paymentTransition oparte o porownanie payment_status (0/1 -> 2), nie status_code"
+ - "Logika preservacji status_code (Phase 62) wydzielona jako $statusOverwriteAllowed — niezalezna decyzja"
+ - "Reuse istniejacej reguly automatyzacji #7 (bez warunku integration_id) zamiast tworzenia osobnej reguly per integracja"
+ - "Backfill idempotentny przez kryterium status_code='nieoplacone' AND payment_status=2 — po updateOrderStatus zamowienie nie spelnia warunku"
+
+patterns-established:
+ - "Re-import emituje payment.status_changed gdy repo zwraca payment_transition i !$wasCreated — wzor dla potencjalnych przyszlych integracji"
+
+duration: ~30min
+started: 2026-05-05T13:00:00Z
+completed: 2026-05-05T13:30:00Z
+---
+
+# Phase 111 Plan 01: Payment Transition Event Summary
+
+**Re-import zamowienia (Allegro + shopPRO) wykrywa tranzycje payment_status 0/1 -> 2 i emituje `payment.status_changed`, dzieki czemu chain reguly automatyzacji #7 zmienia status na `w_realizacji` dla zamowien zaimportowanych przed potwierdzeniem platnosci.**
+
+## Performance
+
+| Metric | Value |
+|--------|-------|
+| Duration | ~30 min |
+| Started | 2026-05-05T13:00:00Z |
+| Completed | 2026-05-05T13:30:00Z |
+| Tasks | 4 completed |
+| Files modified | 5 (1 nowy + 4 zmienione) |
+
+## Acceptance Criteria Results
+
+| Criterion | Status | Notes |
+|-----------|--------|-------|
+| AC-1: Repo wykrywa tranzycje 0/1 → 2 | Pass (code) | Logika rozszerzona w `OrderImportRepository::upsertOrderAggregate`: `paymentTransition = in_array($oldPaymentStatus, [0, 1], true) && $newPaymentStatus === 2`. `statusOverwriteAllowed` zachowuje semantyke Phase 62. Lint OK. Pelny runtime test wymaga zamowienia testowego pre-payment — operator zweryfikuje po deploy. |
+| AC-2: Allegro + shopPRO emituje payment.status_changed | Pass (code) | Oba serwisy emituja event tylko gdy `!$wasCreated && $wasPaymentTransition`. Lint OK. Runtime weryfikacja jak w AC-1 — wymaga zamowienia testowego. |
+| AC-3: Backfill naprawia historyczne dane | Pass (preview) | Skrypt `bin/backfill_payment_transition_111.php` zgodny ze wzorcem Phase 98 (flagi `--dry-run`/`--use-remote`, idempotentny). SQL preview na produkcji znalazl **0 kandydatow** — zamowienie #864 jest juz `wyslane`, brak innych analogicznych. Skrypt zostaje jako safety net. |
+| AC-4: Brak regresji w istniejacym flow | Pass | `order.imported` gate `$wasCreated` nietknięty; nowy emit pod osobnym warunkiem `!$wasCreated && $wasPaymentTransition` — pierwszy import ma `$wasPaymentTransition=false` z definicji (created=true → blok detekcji nieuruchomiony). |
+
+## Accomplishments
+
+- **OrderImportRepository** rozdzielil dwie semantyki: `paymentTransition` (eventowa, oparta o porownanie payment_status) i `statusOverwriteAllowed` (preservacja status_code z Phase 62) — dotychczas mieszane w jednej fladze
+- **AllegroOrderImportService + ShopproOrdersSyncService** spojnie emituja `payment.status_changed` na re-import, bez ruszania gate'u `order.imported` (commit 7eefd1a Phase 98)
+- **Backfill CLI** wzorem Phase 98 — gotowy do uruchomienia po deploy, obecnie no-op (luki historyczne brak)
+- **Reuse reguly #7** zamiast tworzenia osobnej reguly per integracja — chain automatyzacji obejmuje teraz Allegro automatycznie
+
+## Task Commits
+
+| Task | Commit | Type | Description |
+|------|--------|------|-------------|
+| Task 1: OrderImportRepository payment_transition | (uncommitted) | feat | Rozszerzenie detekcji 0/1→2 + getCurrentStatusAndPaymentStatus |
+| Task 2: Allegro+shopPRO emit payment.status_changed | (uncommitted) | feat | Konsumpcja payment_transition flag, emit do AutomationService |
+| Task 3: Backfill CLI | (uncommitted) | feat | bin/backfill_payment_transition_111.php |
+| Task 4: Docs update | (uncommitted) | docs | architecture.md + tech_changelog.md |
+
+**Note:** Wszystkie zmiany wykonane w trybie inline jako jeden working tree state. Atomowe commity zostana wykonane przez transition-phase.
+
+## Files Created/Modified
+
+| File | Change | Purpose |
+|------|--------|---------|
+| `src/Modules/Orders/OrderImportRepository.php` | Modified | `upsertOrderAggregate`: rozdzielenie paymentTransition (0/1→2) i statusOverwriteAllowed; nowa metoda `getCurrentStatusAndPaymentStatus` |
+| `src/Modules/Settings/AllegroOrderImportService.php` | Modified | Emit `payment.status_changed` na payment_transition w re-imporcie |
+| `src/Modules/Settings/ShopproOrdersSyncService.php` | Modified | Emit `payment.status_changed` analogicznie do Allegro |
+| `bin/backfill_payment_transition_111.php` | Created | Jednorazowy CLI dla zamowien `payment_status=2 && status_code='nieoplacone'` |
+| `.paul/codebase/architecture.md` | Modified | Sekcja Order Lifecycle: pkt 2 Re-import (Phase 111) |
+| `.paul/codebase/tech_changelog.md` | Modified | Wpis `2026-05-05 - Phase 111` |
+
+## Decisions Made
+
+| Decision | Rationale | Impact |
+|----------|-----------|--------|
+| `paymentTransition` oparte o porownanie payment_status (nie status_code) | Restrykcyjna definicja "0/1 → 2" wybrana przez uzytkownika — lapie wszystkie tranzycje platnosci niezaleznie od stanu status_code | Bedzie emitowac event tez dla zamowien w nietypowych stanach status (np. po recznym ustawieniu) |
+| Rozdzielenie `statusOverwriteAllowed` od `paymentTransition` | Phase 62 logika preservacji status_code musiala zostac zachowana — laczenie obu w jednej fladze rozszerzaloby zakres nadpisywania status_code | Zachowane zachowanie shopPRO (replacePayments przy paymentTransition) i Phase 62 (preservacja przy ogolnym re-imporcie) |
+| Reuse reguly #7 (bez warunku integration_id) | Regula juz istnieje, dziala dla shopPRO; brak integration_id oznacza universal coverage; chain idempotentny przez OrdersRepository::updateOrderStatus | Nie tworzymy duplikatu reguly per integracja, mniej rzeczy do utrzymania |
+| Backfill idempotentny przez `status_code='nieoplacone' AND payment_status=2` | Po updateOrderStatus → status_code='w_realizacji', warunek SELECT przestaje pasowac | Bezpieczne wielokrotne uruchomienie |
+
+## Deviations from Plan
+
+### Summary
+
+| Type | Count | Impact |
+|------|-------|--------|
+| Auto-fixed | 0 | — |
+| Scope additions | 0 | — |
+| Deferred | 0 | — |
+
+**Total impact:** Plan wykonany dokladnie wg specyfikacji. Brak odchylen.
+
+### Auto-fixed Issues
+
+None — plan executed exactly as written.
+
+### Deferred Items
+
+None — wszystkie 4 zadania ukonczone w zakresie planu.
+
+## Issues Encountered
+
+| Issue | Resolution |
+|-------|------------|
+| Lokalny runtime backfillu niemozliwy (brak `vendor/`) | Zweryfikowano przez `php -l` (lint) + bezposredni SQL preview na DB — 0 kandydatow potwierdzonych. Operator uruchomi skrypt na serwerze po deploy (no-op spodziewany). |
+| Pelny test E2E `payment.status_changed` chain wymaga zamowienia testowego Allegro pre-payment | Udokumentowane w SUMMARY jako "operator weryfikuje po deploy". Code path zweryfikowany staticznie (lint + przeglad logiki + idempotencja przez kryterium). |
+
+## Next Phase Readiness
+
+**Ready:**
+- Hotfix gotowy do deploy (3 zmienione pliki PHP + 1 nowy CLI + 2 docs)
+- Backfill jako safety net (no-op na obecnym stanie DB)
+- Architektura udokumentowana w `.paul/codebase/architecture.md`
+
+**Concerns:**
+- Pelny test E2E wymaga zamowienia testowego Allegro w stanie pre-payment + wymuszenia re-importu po confirm-paid. Operator powinien zweryfikowac na pierwszym realnym przypadku po deploy.
+- Idempotencja przy podwojnym pokryciu shopPRO (osobny `ShopproPaymentStatusSyncService` tez emituje `payment.status_changed`) — w praktyce regula 7 jest idempotentna (`update_order_status` na juz-w_realizacji nie tworzy nowej historii).
+
+**Blockers:**
+- None
+
+---
+*Phase: 111-payment-transition-event, Plan: 01*
+*Completed: 2026-05-05*
diff --git a/bin/backfill_payment_transition_111.php b/bin/backfill_payment_transition_111.php
new file mode 100644
index 0000000..852a80a
--- /dev/null
+++ b/bin/backfill_payment_transition_111.php
@@ -0,0 +1,106 @@
+ $dbConfig */
+$dbConfig = require $basePath . '/config/database.php';
+
+$dryRun = in_array('--dry-run', $argv, true);
+$useRemote = in_array('--use-remote', $argv, true);
+
+if ($useRemote) {
+ $remoteHost = (string) Env::get('DB_HOST_REMOTE', '');
+ if ($remoteHost !== '') {
+ $dbConfig['host'] = $remoteHost;
+ echo '[db] using DB_HOST_REMOTE for this run' . PHP_EOL;
+ }
+}
+
+$pdo = ConnectionFactory::make($dbConfig);
+
+echo 'Backfill 111: orders nieoplacone + payment_status=2 -> w_realizacji' . PHP_EOL;
+echo $dryRun ? '[mode] dry-run' . PHP_EOL : '[mode] apply' . PHP_EOL;
+
+const TARGET_CODE = 'w_realizacji';
+
+$repository = new OrdersRepository($pdo);
+
+$sql = "SELECT id, internal_order_number, source, integration_id "
+ . "FROM orders "
+ . "WHERE source IN ('allegro', 'shoppro') "
+ . "AND payment_status = 2 "
+ . "AND LOWER(COALESCE(status_code, '')) = 'nieoplacone' "
+ . "ORDER BY id ASC";
+
+$stmt = $pdo->prepare($sql);
+$stmt->execute();
+$rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
+
+$total = count($rows);
+$updated = 0;
+$skipped = 0;
+
+echo '[scan] candidates: ' . $total . PHP_EOL;
+
+foreach ($rows as $row) {
+ $orderId = (int) $row['id'];
+ $internalNumber = (string) ($row['internal_order_number'] ?? '');
+ $source = (string) ($row['source'] ?? '');
+ $integrationId = (int) ($row['integration_id'] ?? 0);
+
+ $label = sprintf('#%d %s (source=%s, integration=%d)', $orderId, $internalNumber, $source, $integrationId);
+
+ if ($dryRun) {
+ echo ' [dry-run] ' . $label . ' -> ' . TARGET_CODE . PHP_EOL;
+ continue;
+ }
+
+ try {
+ $ok = $repository->updateOrderStatus(
+ $orderId,
+ TARGET_CODE,
+ 'import',
+ 'Backfill 111'
+ );
+
+ if ($ok) {
+ $updated++;
+ echo ' [ok] ' . $label . ' -> ' . TARGET_CODE . PHP_EOL;
+ } else {
+ $skipped++;
+ echo ' [skip] ' . $label . ' (updateOrderStatus returned false)' . PHP_EOL;
+ }
+ } catch (Throwable $exception) {
+ $skipped++;
+ fwrite(STDERR, ' [err] ' . $label . ': ' . $exception->getMessage() . PHP_EOL);
+ }
+}
+
+echo PHP_EOL;
+echo 'Backfill 111: total=' . $total . ' updated=' . $updated . ' skipped=' . $skipped . PHP_EOL;
+echo 'Done.' . PHP_EOL;
diff --git a/src/Modules/Orders/OrderImportRepository.php b/src/Modules/Orders/OrderImportRepository.php
index 62c2e68..59ffda5 100644
--- a/src/Modules/Orders/OrderImportRepository.php
+++ b/src/Modules/Orders/OrderImportRepository.php
@@ -39,12 +39,18 @@ final class OrderImportRepository
$existingOrderId = $this->findOrderIdBySource($source, $sourceOrderId);
$created = $existingOrderId === null;
$paymentTransition = false;
+ $statusOverwriteAllowed = false;
if (!$created) {
- $currentStatus = $this->getCurrentStatus($existingOrderId);
+ $existing = $this->getCurrentStatusAndPaymentStatus($existingOrderId);
+ $currentStatus = $existing['status_code'];
+ $oldPaymentStatus = $existing['payment_status'];
$newPaymentStatus = (int) ($orderData['payment_status'] ?? 0);
- $paymentTransition = $currentStatus === 'nieoplacone' && $newPaymentStatus === 2;
- if (!$paymentTransition) {
+
+ $paymentTransition = in_array($oldPaymentStatus, [0, 1], true) && $newPaymentStatus === 2;
+ $statusOverwriteAllowed = $currentStatus === 'nieoplacone' && $newPaymentStatus === 2;
+
+ if (!$statusOverwriteAllowed) {
$orderData['status_code'] = $currentStatus;
}
}
@@ -61,7 +67,7 @@ final class OrderImportRepository
$this->replacePayments($orderId, $payments);
$this->replaceShipments($orderId, $shipments);
$this->replaceStatusHistory($orderId, $statusHistory);
- } elseif ($paymentTransition) {
+ } elseif ($paymentTransition || $statusOverwriteAllowed) {
$this->replacePayments($orderId, $payments);
}
@@ -101,15 +107,25 @@ final class OrderImportRepository
return $id > 0 ? $id : null;
}
- private function getCurrentStatus(int $orderId): string
+ /**
+ * @return array{status_code:string, payment_status:int}
+ */
+ private function getCurrentStatusAndPaymentStatus(int $orderId): array
{
$statement = $this->pdo->prepare(
- 'SELECT status_code FROM orders WHERE id = :id LIMIT 1'
+ 'SELECT status_code, payment_status FROM orders WHERE id = :id LIMIT 1'
);
$statement->execute(['id' => $orderId]);
- $value = $statement->fetchColumn();
+ $row = $statement->fetch(PDO::FETCH_ASSOC);
- return strtolower(trim((string) ($value ?: '')));
+ if (!is_array($row)) {
+ return ['status_code' => '', 'payment_status' => 0];
+ }
+
+ return [
+ 'status_code' => strtolower(trim((string) ($row['status_code'] ?? ''))),
+ 'payment_status' => (int) ($row['payment_status'] ?? 0),
+ ];
}
/**
diff --git a/src/Modules/Settings/AllegroOrderImportService.php b/src/Modules/Settings/AllegroOrderImportService.php
index f70ae7b..3ff4fa1 100644
--- a/src/Modules/Settings/AllegroOrderImportService.php
+++ b/src/Modules/Settings/AllegroOrderImportService.php
@@ -104,6 +104,16 @@ final class AllegroOrderImportService
'new_payment_status' => (string) ($mapped['order']['payment_status'] ?? ''),
]);
}
+
+ $wasPaymentTransition = !empty($saveResult['payment_transition']);
+ if (!$wasCreated && $wasPaymentTransition && $this->automationService !== null) {
+ $this->automationService->trigger('payment.status_changed', $savedOrderId, [
+ 'source' => IntegrationSources::ALLEGRO,
+ 'integration_id' => (int) ($mapped['order']['integration_id'] ?? 0),
+ 'old_payment_status' => '',
+ 'new_payment_status' => (string) ($mapped['order']['payment_status'] ?? ''),
+ ]);
+ }
}
return [
diff --git a/src/Modules/Settings/ShopproOrdersSyncService.php b/src/Modules/Settings/ShopproOrdersSyncService.php
index 55f44ac..dd69543 100644
--- a/src/Modules/Settings/ShopproOrdersSyncService.php
+++ b/src/Modules/Settings/ShopproOrdersSyncService.php
@@ -278,6 +278,15 @@ final class ShopproOrdersSyncService
'new_payment_status' => (string) ($aggregate['order']['payment_status'] ?? ''),
]);
}
+
+ if ($savedOrderId > 0 && !$wasCreated && $wasPaymentTransition && $this->automationService !== null) {
+ $this->automationService->trigger('payment.status_changed', $savedOrderId, [
+ 'source' => 'shoppro',
+ 'integration_id' => $integrationId,
+ 'old_payment_status' => '',
+ 'new_payment_status' => (string) ($aggregate['order']['payment_status'] ?? ''),
+ ]);
+ }
} catch (Throwable $exception) {
$result['failed'] = (int) $result['failed'] + 1;
$errors = is_array($result['errors']) ? $result['errors'] : [];