feat(111): payment transition event for Allegro+shopPRO re-import
Re-import zamowienia 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 dla zamowien payment_status=2 && status_code='nieoplacone' (Allegro + shopPRO) - Naprawa luki znalezionej w analizie zamowienia #864 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,9 +12,9 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
|||||||
|
|
||||||
| Attribute | Value |
|
| Attribute | Value |
|
||||||
|-----------|-------|
|
|-----------|-------|
|
||||||
| Version | 3.4.0 |
|
| Version | 3.5.0 |
|
||||||
| Status | v3.4 shipped - Statistics Summary complete |
|
| Status | v3.5 shipped - Payment Transition Event hotfix complete |
|
||||||
| Last Updated | 2026-04-28 |
|
| Last Updated | 2026-05-05 |
|
||||||
|
|
||||||
## Requirements
|
## 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] 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] 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] 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
|
### Deferred
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ orderPRO to narzedzie do wielokanalowego zarzadzania sprzedaza. Projekt przechod
|
|||||||
|
|
||||||
## Current Milestone
|
## Current Milestone
|
||||||
|
|
||||||
Brak aktywnego milestone - v3.4 zamkniety. Nastepny milestone do zaplanowania.
|
Brak aktywnego milestone - v3.5 zamkniety. Nastepny milestone do zaplanowania.
|
||||||
|
|
||||||
## Next Milestone
|
## Next Milestone
|
||||||
|
|
||||||
@@ -19,6 +19,19 @@ Kandydaci w kolejce:
|
|||||||
|
|
||||||
## Completed Milestones
|
## Completed Milestones
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>v3.5 Payment Transition Event - 2026-05-05 (1 phase, 1 plan)</summary>
|
||||||
|
|
||||||
|
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/`
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>v3.4 Statistics Summary - 2026-04-28 (1 phase, 1 plan)</summary>
|
<summary>v3.4 Statistics Summary - 2026-04-28 (1 phase, 1 plan)</summary>
|
||||||
|
|
||||||
|
|||||||
@@ -9,34 +9,33 @@ See: .paul/PROJECT.md (updated 2026-04-28)
|
|||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v3.4 Statistics Summary - COMPLETE
|
Milestone: v3.5 Payment Transition Event (hotfix) — COMPLETE
|
||||||
Phase: 110 of 110 - COMPLETE
|
Phase: 111 of 111 (Payment Transition Event) — COMPLETE
|
||||||
Plan: 110-01 - COMPLETE
|
Plan: 111-01 — COMPLETE
|
||||||
Version: 3.4.0
|
Status: v3.5 shipped, awaiting transition (commit) i nastepny milestone
|
||||||
Status: v3.4 shipped - gotowy do nastepnego milestone
|
Last activity: 2026-05-05 — UNIFY Phase 111 / Plan 111-01 complete
|
||||||
|
|
||||||
Last activity: 2026-04-28 - UNIFY Phase 110 / v3.4 milestone complete
|
|
||||||
|
|
||||||
Progress:
|
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
|
## Loop Position
|
||||||
|
|
||||||
Current loop state:
|
Current loop state:
|
||||||
```
|
```
|
||||||
v3.4 milestone:
|
v3.5 milestone:
|
||||||
Phase 110 (Statistics Summary):
|
Phase 111 (Payment Transition Event):
|
||||||
Plan 110-01: PLAN done APPLY done UNIFY done
|
Plan 111-01: PLAN done APPLY done UNIFY done
|
||||||
-> Phase 110 closed
|
-> Phase 111 closed
|
||||||
-> v3.4 milestone closed
|
-> v3.5 milestone closed (pending transition commit)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-28
|
Last session: 2026-05-05
|
||||||
Stopped at: v3.4 milestone closed
|
Stopped at: v3.5 milestone closed
|
||||||
Next action: /paul:milestone - wybor i zaplanowanie nastepnego milestone
|
Next action: transition-phase (PROJECT/ROADMAP update + git commit), nastepnie /paul:milestone
|
||||||
Resume file: .paul/phases/110-statistics-summary/110-01-SUMMARY.md
|
Resume file: .paul/phases/111-payment-transition-event/111-01-SUMMARY.md
|
||||||
|
|
||||||
## Git State
|
## Git State
|
||||||
|
|
||||||
|
|||||||
21
.paul/changelog/2026-05-05.md
Normal file
21
.paul/changelog/2026-05-05.md
Normal file
@@ -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`
|
||||||
@@ -65,8 +65,9 @@ HTTP Request
|
|||||||
|
|
||||||
### Order Lifecycle
|
### Order Lifecycle
|
||||||
1. **Import** — Cron handler → API client → `OrderImportService` → `OrdersRepository::insertOrder()` → `AutomationService::executeForNewOrder()`
|
1. **Import** — Cron handler → API client → `OrderImportService` → `OrdersRepository::insertOrder()` → `AutomationService::executeForNewOrder()`
|
||||||
2. **Status update** — `OrdersController::updateStatus()` → `OrdersRepository::updateStatus()` → automation check
|
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 sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` → carrier API
|
3. **Status update** — `OrdersController::updateStatus()` → `OrdersRepository::updateStatus()` → automation check
|
||||||
|
4. **Status sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` → carrier API
|
||||||
|
|
||||||
### Statistics Summary
|
### Statistics Summary
|
||||||
1. **Request** — `/statistics/summary` → `OrdersStatisticsController::summary()`
|
1. **Request** — `/statistics/summary` → `OrdersStatisticsController::summary()`
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# Technical Changelog
|
# 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
|
## 2026-04-28 - Phase 110 Plan 01: Statistics Summary
|
||||||
|
|
||||||
**Co zrobiono:**
|
**Co zrobiono:**
|
||||||
|
|||||||
318
.paul/phases/111-payment-transition-event/111-01-PLAN.md
Normal file
318
.paul/phases/111-payment-transition-event/111-01-PLAN.md
Normal file
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## 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`
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
<clarifications>
|
||||||
|
- **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.
|
||||||
|
</clarifications>
|
||||||
|
|
||||||
|
## 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
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Rozszerz OrderImportRepository o detekcje tranzycji 0/1 → 2</name>
|
||||||
|
<files>src/Modules/Orders/OrderImportRepository.php</files>
|
||||||
|
<action>
|
||||||
|
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).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- `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/`).
|
||||||
|
</verify>
|
||||||
|
<done>AC-1 spelnione: payment_transition oparte o porownanie payment_status, status_code chroniony zgodnie z dotychczasowa logika preservacji.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Emit payment.status_changed w Allegro + shopPRO import services</name>
|
||||||
|
<files>src/Modules/Settings/AllegroOrderImportService.php, src/Modules/Settings/ShopproOrdersSyncService.php</files>
|
||||||
|
<action>
|
||||||
|
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).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- `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'.
|
||||||
|
</verify>
|
||||||
|
<done>AC-2 spelnione: oba serwisy importu emituja `payment.status_changed` na payment_transition. AC-4 zachowane: order.imported nadal gated.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Backfill CLI dla istniejacych zamowien z luka platnosci</name>
|
||||||
|
<files>bin/backfill_payment_transition_111.php</files>
|
||||||
|
<action>
|
||||||
|
Stworz nowy plik wzorem `bin/backfill_shipped_status_98.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require __DIR__ . '/../bootstrap/app.php';
|
||||||
|
|
||||||
|
/** @var App\Core\Application $app */
|
||||||
|
$app = require __DIR__ . '/../bootstrap/app.php';
|
||||||
|
|
||||||
|
$pdo = $app->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).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- `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'
|
||||||
|
</verify>
|
||||||
|
<done>AC-3 spelnione: backfill naprawia historyczne dane idempotentnie.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 4: Aktualizacja dokumentacji codebase (architecture + tech_changelog)</name>
|
||||||
|
<files>.paul/codebase/architecture.md, .paul/codebase/tech_changelog.md</files>
|
||||||
|
<action>
|
||||||
|
`.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.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
Manualny przeglad obu plikow — sekcje istnieja, formatowanie spojne z reszta.
|
||||||
|
</verify>
|
||||||
|
<done>Dokumentacja codebase zsynchronizowana z kodem.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
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
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Po zakonczeniu UNIFY: `.paul/phases/111-payment-transition-event/111-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
151
.paul/phases/111-payment-transition-event/111-01-SUMMARY.md
Normal file
151
.paul/phases/111-payment-transition-event/111-01-SUMMARY.md
Normal file
@@ -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*
|
||||||
106
bin/backfill_payment_transition_111.php
Normal file
106
bin/backfill_payment_transition_111.php
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backfill (Phase 111): zamowienia z payment_status=2 i status_code='nieoplacone'
|
||||||
|
* (source=allegro lub shoppro) zostaja przestawione na status 'w_realizacji'.
|
||||||
|
* Zmiana idzie przez OrdersRepository::updateOrderStatus, dzieki czemu powstaje
|
||||||
|
* wpis w order_status_history i order_activity_log.
|
||||||
|
*
|
||||||
|
* Naprawia luke z Phase 98 (gate $wasCreated dla order.imported) — zamowienia
|
||||||
|
* zaimportowane przed potwierdzeniem platnosci, ktore utknely w 'nieoplacone'
|
||||||
|
* mimo opłacenia.
|
||||||
|
*
|
||||||
|
* Idempotentny: drugie uruchomienie na czystej bazie nie zrobi nic.
|
||||||
|
*
|
||||||
|
* Flagi:
|
||||||
|
* --dry-run pokaz tylko liste kandydatow, bez zmian
|
||||||
|
* --use-remote uzyj DB_HOST_REMOTE zamiast DB_HOST (dla operacji recznych agenta)
|
||||||
|
*/
|
||||||
|
|
||||||
|
use App\Core\Database\ConnectionFactory;
|
||||||
|
use App\Core\Support\Env;
|
||||||
|
use App\Modules\Orders\OrdersRepository;
|
||||||
|
|
||||||
|
$basePath = dirname(__DIR__);
|
||||||
|
require $basePath . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
Env::load($basePath . '/.env');
|
||||||
|
|
||||||
|
/** @var array<string, mixed> $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;
|
||||||
@@ -39,12 +39,18 @@ final class OrderImportRepository
|
|||||||
$existingOrderId = $this->findOrderIdBySource($source, $sourceOrderId);
|
$existingOrderId = $this->findOrderIdBySource($source, $sourceOrderId);
|
||||||
$created = $existingOrderId === null;
|
$created = $existingOrderId === null;
|
||||||
$paymentTransition = false;
|
$paymentTransition = false;
|
||||||
|
$statusOverwriteAllowed = false;
|
||||||
|
|
||||||
if (!$created) {
|
if (!$created) {
|
||||||
$currentStatus = $this->getCurrentStatus($existingOrderId);
|
$existing = $this->getCurrentStatusAndPaymentStatus($existingOrderId);
|
||||||
|
$currentStatus = $existing['status_code'];
|
||||||
|
$oldPaymentStatus = $existing['payment_status'];
|
||||||
$newPaymentStatus = (int) ($orderData['payment_status'] ?? 0);
|
$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;
|
$orderData['status_code'] = $currentStatus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +67,7 @@ final class OrderImportRepository
|
|||||||
$this->replacePayments($orderId, $payments);
|
$this->replacePayments($orderId, $payments);
|
||||||
$this->replaceShipments($orderId, $shipments);
|
$this->replaceShipments($orderId, $shipments);
|
||||||
$this->replaceStatusHistory($orderId, $statusHistory);
|
$this->replaceStatusHistory($orderId, $statusHistory);
|
||||||
} elseif ($paymentTransition) {
|
} elseif ($paymentTransition || $statusOverwriteAllowed) {
|
||||||
$this->replacePayments($orderId, $payments);
|
$this->replacePayments($orderId, $payments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,15 +107,25 @@ final class OrderImportRepository
|
|||||||
return $id > 0 ? $id : null;
|
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(
|
$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]);
|
$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),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -104,6 +104,16 @@ final class AllegroOrderImportService
|
|||||||
'new_payment_status' => (string) ($mapped['order']['payment_status'] ?? ''),
|
'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 [
|
return [
|
||||||
|
|||||||
@@ -278,6 +278,15 @@ final class ShopproOrdersSyncService
|
|||||||
'new_payment_status' => (string) ($aggregate['order']['payment_status'] ?? ''),
|
'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) {
|
} catch (Throwable $exception) {
|
||||||
$result['failed'] = (int) $result['failed'] + 1;
|
$result['failed'] = (int) $result['failed'] + 1;
|
||||||
$errors = is_array($result['errors']) ? $result['errors'] : [];
|
$errors = is_array($result['errors']) ? $result['errors'] : [];
|
||||||
|
|||||||
Reference in New Issue
Block a user