feat(112): re-import data protection — delta-only re-import + project_generated preservation
Phase 112 / Plan 112-01 complete (v3.6): - OrderImportRepository::upsertOrderAggregate split into create vs re-import paths - replaceAddresses/Items/Notes/Shipments/StatusHistory invoked only on first import - new updateOrderDelta() narrows UPDATE to status_code (cond.), payment_status, total_paid, is_canceled_by_buyer, source_updated_at, payload_json, fetched_at - source-side cancellation override (is_canceled_by_buyer=1 OR pull status_code='anulowane') - identical-payload no-op guard via normalizePayloadJson() - fixes case #882: order_items.id stable, project_generated (Phase 97) preserved - Phase 111 payment.status_changed emit retained without regression 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.5.0 |
|
| Version | 3.6.0 |
|
||||||
| Status | v3.5 shipped - Payment Transition Event hotfix complete |
|
| Status | v3.6 shipped - Re-import Data Protection hotfix complete |
|
||||||
| Last Updated | 2026-05-05 |
|
| Last Updated | 2026-05-07 |
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -115,6 +115,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
|||||||
- [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
|
- [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
|
||||||
|
- [x] Re-import istniejacego zamowienia jest delta-only: skip dla pozycji/adresow/notatek (stabilne `order_items.id`, ochrona `project_generated`), zawezony `updateOrderDelta()`, propagacja anulowania ze zrodla, identical-payload no-op guard (case #882) — Phase 112
|
||||||
|
|
||||||
### Deferred
|
### Deferred
|
||||||
|
|
||||||
@@ -193,6 +194,8 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
|
|||||||
| Push waybilla do Allegro checkout forms wykonywany tylko dla zamowien source=allegro i jest niekrytyczny dla lokalnego tworzenia paczki | Eliminacja recznego kroku po stronie Allegro bez ryzyka utraty lokalnie utworzonej przesylki przy bledzie API | 2026-03-28 | Active |
|
| Push waybilla do Allegro checkout forms wykonywany tylko dla zamowien source=allegro i jest niekrytyczny dla lokalnego tworzenia paczki | Eliminacja recznego kroku po stronie Allegro bez ryzyka utraty lokalnie utworzonej przesylki przy bledzie API | 2026-03-28 | Active |
|
||||||
| Event `order.imported` emitowany tylko przy pierwszym imporcie zamowienia | Unikniecie duplikatow reakcji automatyzacji przy kolejnych synchronizacjach | 2026-04-15 | Active |
|
| Event `order.imported` emitowany tylko przy pierwszym imporcie zamowienia | Unikniecie duplikatow reakcji automatyzacji przy kolejnych synchronizacjach | 2026-04-15 | Active |
|
||||||
| Preset przesylek nadpisuje wylacznie wymiary+wage + auto-submit po autofill | Single responsibility preseta + szybszy flow operatora | 2026-04-17 | Active |
|
| Preset przesylek nadpisuje wylacznie wymiary+wage + auto-submit po autofill | Single responsibility preseta + szybszy flow operatora | 2026-04-17 | Active |
|
||||||
|
| Re-import istniejacego zamowienia jest delta-only — `replaceAddresses/Items/Notes` tylko przy `created=true`; `updateOrderDelta()` zawezony do payment_status/total_paid/status_code/is_canceled_by_buyer/source_updated_at/payload_json/fetched_at | Zamowienia zarzadzane sa w orderPRO (nie w zrodle), wiec re-import nie powinien nadpisywac stanu lokalnego ani lamac stabilnosci `order_items.id` (case #882: znikajace `project_generated`) | 2026-05-07 | Active |
|
||||||
|
| Identical-payload no-op guard w re-imporcie via `normalizePayloadJson()` (decode->encode->compare) | Eliminacja niepotrzebnych write'ow do binloga/replikacji przy cyklicznym imporcie tych samych zamowien; fail-open gdy klucze JSON sa reorderowane miedzy syncami | 2026-05-07 | Active |
|
||||||
| Statistics channelSql: explicit `COLLATE utf8mb4_unicode_ci` na CASE z `CAST(integration_id AS CHAR)` | Unikniecie `1271 Illegal mix of collations` w `IN (...)` z parametrami bindowanymi; pattern dla przyszlych agregacji per-integration | 2026-04-19 | Active |
|
| Statistics channelSql: explicit `COLLATE utf8mb4_unicode_ci` na CASE z `CAST(integration_id AS CHAR)` | Unikniecie `1271 Illegal mix of collations` w `IN (...)` z parametrami bindowanymi; pattern dla przyszlych agregacji per-integration | 2026-04-19 | Active |
|
||||||
| Statistics netto fallback `ROUND(gross / 1.23, 2)` gdy `total_without_tax` puste | shopPRO nie wysyla netto ani w zamowieniu ani w `order_items`; tymczasowy fallback — docelowy fix w `.paul/TODO.md` (STAT-NET) | 2026-04-19 | Active |
|
| Statistics netto fallback `ROUND(gross / 1.23, 2)` gdy `total_without_tax` puste | shopPRO nie wysyla netto ani w zamowieniu ani w `order_items`; tymczasowy fallback — docelowy fix w `.paul/TODO.md` (STAT-NET) | 2026-04-19 | Active |
|
||||||
| ON DUPLICATE KEY UPDATE created_at = created_at dla idempotentnego markSent() | Unikniece silent failure i race condition przy rownolegych cronach; thread-safe bez wyjatkow | 2026-04-25 | Active |
|
| ON DUPLICATE KEY UPDATE created_at = created_at dla idempotentnego markSent() | Unikniece silent failure i race condition przy rownolegych cronach; thread-safe bez wyjatkow | 2026-04-25 | Active |
|
||||||
@@ -232,6 +235,6 @@ Quick Reference:
|
|||||||
|
|
||||||
---
|
---
|
||||||
*PROJECT.md — Updated when requirements or context change*
|
*PROJECT.md — Updated when requirements or context change*
|
||||||
*Last updated: 2026-04-28 after v3.4 Statistics Summary milestone completion (Phase 110)*
|
*Last updated: 2026-05-07 after v3.6 Re-import Data Protection milestone completion (Phase 112)*
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ orderPRO to narzedzie do wielokanalowego zarzadzania sprzedaza. Projekt przechod
|
|||||||
|
|
||||||
## Current Milestone
|
## Current Milestone
|
||||||
|
|
||||||
Brak aktywnego milestone - v3.5 zamkniety. Nastepny milestone do zaplanowania.
|
Brak aktywnego milestone - v3.6 zamkniety. Nastepny milestone do zaplanowania.
|
||||||
|
|
||||||
## Next Milestone
|
## Next Milestone
|
||||||
|
|
||||||
@@ -19,6 +19,19 @@ Kandydaci w kolejce:
|
|||||||
|
|
||||||
## Completed Milestones
|
## Completed Milestones
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>v3.6 Re-import Data Protection - 2026-05-07 (1 phase, 1 plan)</summary>
|
||||||
|
|
||||||
|
Re-import istniejacego zamowienia (Allegro + shopPRO) jest delta-only: `replaceAddresses/Items/Notes` wywolywane wylacznie przy pierwszym imporcie, nowy `updateOrderDelta()` aktualizuje tylko pola realnie zmieniajace sie ze zrodla. Stabilne `order_items.id` chronia `project_generated` (Phase 97) i flow generowania PSD. Dodatkowo: propagacja anulowania ze zrodla (override niezalezny od statusOverwriteAllowed) i identical-payload no-op guard. Naprawa case #882 (znikajaca flaga "Projekt" po re-imporcie wymuszanym przez `payment.status_changed` z Phase 111).
|
||||||
|
|
||||||
|
| Phase | Name | Plans | Status |
|
||||||
|
|-------|------|-------|--------|
|
||||||
|
| 112 | Re-import Data Protection | 1/1 | Complete |
|
||||||
|
|
||||||
|
Archive: `.paul/phases/112-reimport-data-protection/`
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>v3.5 Payment Transition Event - 2026-05-05 (1 phase, 1 plan)</summary>
|
<summary>v3.5 Payment Transition Event - 2026-05-05 (1 phase, 1 plan)</summary>
|
||||||
|
|
||||||
@@ -467,4 +480,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
|
|||||||
|
|
||||||
---
|
---
|
||||||
*Roadmap created: 2026-03-12*
|
*Roadmap created: 2026-03-12*
|
||||||
*Last updated: 2026-04-28 - v3.4 Statistics Summary milestone closed (Phase 110)*
|
*Last updated: 2026-05-07 - v3.6 Re-import Data Protection milestone closed (Phase 112)*
|
||||||
|
|||||||
@@ -2,49 +2,56 @@
|
|||||||
|
|
||||||
## Project Reference
|
## Project Reference
|
||||||
|
|
||||||
See: .paul/PROJECT.md (updated 2026-04-28)
|
See: .paul/PROJECT.md (updated 2026-05-07)
|
||||||
|
|
||||||
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
|
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
|
||||||
**Current focus:** Brak aktywnego milestone - v3.4 zamkniety
|
**Current focus:** Brak aktywnego milestone - v3.6 zamkniety
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v3.5 Payment Transition Event (hotfix) — COMPLETE
|
Milestone: v3.6 Re-import Data Protection (hotfix) — COMPLETE
|
||||||
Phase: 111 of 111 (Payment Transition Event) — COMPLETE
|
Phase: 112 of 112 (Re-import Data Protection) — COMPLETE
|
||||||
Plan: 111-01 — COMPLETE
|
Plan: 112-01 — COMPLETE
|
||||||
Status: v3.5 shipped, awaiting transition (commit) i nastepny milestone
|
Status: v3.6 shipped, oczekiwanie na nastepny milestone
|
||||||
Last activity: 2026-05-05 — UNIFY Phase 111 / Plan 111-01 complete
|
Last activity: 2026-05-07 — Transition Phase 112 / Plan 112-01 complete (PROJECT.md + ROADMAP.md updated, git commit)
|
||||||
|
|
||||||
Progress:
|
Progress:
|
||||||
- Milestone v3.5: [##########] 100% (1/1 phases, 1/1 plans)
|
- Milestone v3.6: [##########] 100% (1/1 phases, 1/1 plans)
|
||||||
- Phase 111: [##########] 100%
|
- Phase 112: [##########] 100%
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
Current loop state:
|
Current loop state:
|
||||||
```
|
```
|
||||||
v3.5 milestone:
|
v3.6 milestone:
|
||||||
Phase 111 (Payment Transition Event):
|
Phase 112 (Re-import Data Protection):
|
||||||
Plan 111-01: PLAN done APPLY done UNIFY done
|
Plan 112-01: PLAN ✓ → APPLY ✓ → UNIFY ✓
|
||||||
-> Phase 111 closed
|
-> Phase 112 closed
|
||||||
-> v3.5 milestone closed (pending transition commit)
|
-> v3.6 milestone closed (pending transition commit)
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
|
✓ ✓ ✓ [Loop complete]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-05-05
|
Last session: 2026-05-07
|
||||||
Stopped at: v3.5 milestone closed
|
Stopped at: v3.6 milestone closed (Phase 112 transitioned)
|
||||||
Next action: transition-phase (PROJECT/ROADMAP update + git commit), nastepnie /paul:milestone
|
Next action: manualne testy AC-1..AC-7 na zywej bazie (XAMPP), backfill #882, nastepnie /paul:milestone (nowy milestone)
|
||||||
Resume file: .paul/phases/111-payment-transition-event/111-01-SUMMARY.md
|
Resume file: .paul/phases/112-reimport-data-protection/112-01-SUMMARY.md
|
||||||
|
|
||||||
## Git State
|
## Git State
|
||||||
|
|
||||||
Last commit: feat(110): statistics summary
|
Last commit: 0e457ae update
|
||||||
Branch: main
|
Branch: main
|
||||||
Feature branches merged: none
|
Feature branches merged: none
|
||||||
|
|
||||||
## Pending Actions
|
## Pending Actions
|
||||||
|
|
||||||
|
- Manualne testy AC-1..AC-7 dla Phase 112 na zywej bazie (XAMPP online)
|
||||||
|
- Backfill zamowienia #882 — operator robi recznie po wdrozeniu (poza zakresem planu)
|
||||||
- Uruchom migracje gdy XAMPP online: `php bin/migrate.php` (delivery_statuses)
|
- Uruchom migracje gdy XAMPP online: `php bin/migrate.php` (delivery_statuses)
|
||||||
- Recznie odtworzyc istniejace reguly automatyzacji z grupowymi kluczami (BREAKING z 108-02)
|
- Recznie odtworzyc istniejace reguly automatyzacji z grupowymi kluczami (BREAKING z 108-02)
|
||||||
|
|
||||||
@@ -72,4 +79,4 @@ Feature branches merged: none
|
|||||||
|
|
||||||
- Local HTTP verification blocked by MySQL/XAMPP connection refused.
|
- Local HTTP verification blocked by MySQL/XAMPP connection refused.
|
||||||
- PHPUnit not run: `composer` unavailable in PATH and `vendor/` absent.
|
- PHPUnit not run: `composer` unavailable in PATH and `vendor/` absent.
|
||||||
- Sonar issue import to `DOCS/todo.md` not performed because SonarQube MCP/resources are unavailable in this session.
|
- Sonar issue import to `DOCS/todo.md` not performed because SonarQube MCP/resources are unavailable in this session.
|
||||||
|
|||||||
20
.paul/changelog/2026-05-07.md
Normal file
20
.paul/changelog/2026-05-07.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# 2026-05-07
|
||||||
|
|
||||||
|
## Co zrobiono
|
||||||
|
|
||||||
|
- [Phase 112, Plan 01] Re-import Data Protection — re-import istniejacego zamowienia jest delta-only
|
||||||
|
- Task 1: `replaceAddresses/replaceItems/replaceNotes/replaceShipments/replaceStatusHistory` przeniesione pod galaz `if ($created)` w `OrderImportRepository::upsertOrderAggregate` — `order_items.id` i flagi lokalne (project_generated z Phase 97) sa stabilne miedzy re-importami
|
||||||
|
- Task 2: nowa metoda `updateOrderDelta()` aktualizuje wylacznie `status_code` (warunkowo), `payment_status`, `total_paid`, `is_canceled_by_buyer`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`. Dodana propagacja anulowania ze zrodla (`is_canceled_by_buyer=1` LUB pull `status_code='anulowane'`) i identical-payload no-op guard via `normalizePayloadJson()`
|
||||||
|
- Task 3: `.paul/codebase/architecture.md` (sekcja Re-import) i `.paul/codebase/tech_changelog.md` (wpis 2026-05-07) zaktualizowane
|
||||||
|
- Bug case #882 (znikajaca flaga "Projekt" po re-imporcie wymuszanym przez `payment.status_changed`) przyczynowo naprawiony
|
||||||
|
- `php -l src/Modules/Orders/OrderImportRepository.php` -> No syntax errors
|
||||||
|
- Manualne AC-1..AC-7 odlozone — XAMPP/MySQL nieosiagalne w sesji
|
||||||
|
|
||||||
|
## Zmienione pliki
|
||||||
|
|
||||||
|
- `src/Modules/Orders/OrderImportRepository.php`
|
||||||
|
- `.paul/codebase/architecture.md`
|
||||||
|
- `.paul/codebase/tech_changelog.md`
|
||||||
|
- `.paul/phases/112-reimport-data-protection/112-01-PLAN.md`
|
||||||
|
- `.paul/phases/112-reimport-data-protection/112-01-SUMMARY.md`
|
||||||
|
- `.paul/STATE.md`
|
||||||
@@ -65,7 +65,7 @@ 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. **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`).
|
2. **Re-import (Phase 111 + 112)** — `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`). **Phase 112-01 (delta-only re-import):** przy `created=false` repo nie wywoluje `replaceAddresses/replaceItems/replaceNotes/replaceShipments/replaceStatusHistory` — `order_items.id` i flagi lokalne (np. `project_generated` z Phase 97) pozostaja stabilne. `updateOrderDelta()` aktualizuje wylacznie `status_code` (warunkowo, z propagacja anulowania), `payment_status`, `total_paid`, `is_canceled_by_buyer`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`. Anulowanie ze zrodla (`is_canceled_by_buyer=1` lub zmapowany pull `status_code='anulowane'`) nadpisuje preservacje statusu. Identical-payload guard (`normalizePayloadJson`) pomija UPDATE gdy znormalizowany payload nie rozni sie od DB i brak innych tranzycji.
|
||||||
3. **Status update** — `OrdersController::updateStatus()` → `OrdersRepository::updateStatus()` → automation check
|
3. **Status update** — `OrdersController::updateStatus()` → `OrdersRepository::updateStatus()` → automation check
|
||||||
4. **Status sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` → carrier API
|
4. **Status sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` → carrier API
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,25 @@
|
|||||||
# Technical Changelog
|
# Technical Changelog
|
||||||
|
|
||||||
|
## 2026-05-07 - Phase 112 Plan 01: Re-import Data Protection
|
||||||
|
|
||||||
|
**Co zrobiono:**
|
||||||
|
- `OrderImportRepository::upsertOrderAggregate` - rozdzielono sciezke `created` (pierwszy import) od `else` (re-import). `replaceAddresses`, `replaceItems`, `replaceNotes`, `replacePayments`, `replaceShipments`, `replaceStatusHistory` wywolywane sa teraz wylacznie przy pierwszym imporcie. Logika `paymentTransition` / `statusOverwriteAllowed` (Phase 111) i preservacja `status_code` (Phase 62) bez zmian.
|
||||||
|
- `OrderImportRepository::updateOrderDelta()` - nowa prywatna metoda zastepujaca `updateOrder()`. Aktualizuje wylacznie `status_code`, `payment_status`, `total_paid`, `is_canceled_by_buyer`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`. Pozostale kolumny zamowienia nie sa nadpisywane przez re-import.
|
||||||
|
- Propagacja anulowania: gdy `is_canceled_by_buyer=1` ze zrodla LUB zmapowany pull `status_code='anulowane'` (Phase 75/83), wymuszone jest `orders.status_code='anulowane'` niezaleznie od `statusOverwriteAllowed`.
|
||||||
|
- Identical-payload guard: porownanie znormalizowanego `payload_json` (nowy vs aktualny w DB). Identyczny payload + brak `paymentTransition`/`statusOverwriteAllowed`/`cancelledBySource` -> commit transakcji bez wywolywania UPDATE; `fetched_at` i `updated_at` pozostaja niezmienione.
|
||||||
|
- `OrderImportRepository::getCurrentOrderState()` - rozszerzenie `getCurrentStatusAndPaymentStatus()` o kolumne `payload_json` (jeden SELECT zamiast dwoch).
|
||||||
|
- `OrderImportRepository::normalizePayloadJson()` - nowy helper deserializujacy/reserializujacy payload do porownywalnej formy (`JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES`).
|
||||||
|
|
||||||
|
**Dlaczego:**
|
||||||
|
- Bug case #882: po re-imporcie zamowienia produkty w `/orders/{id}` wyswietlaly badge "Brak projektu" mimo wczesniej wygenerowanych projektow PSD. Przyczyna: `replaceItems()` wykonywal DELETE+INSERT na `order_items` przy kazdym re-imporcie, co (a) zerowalo flagi `project_generated`/`project_generated_at` (Phase 97) na default 0/NULL i (b) zmienialo `order_items.id`, co lamie referencje skryptu batch `tools/generowanie/_batch_run.sh` (`UPDATE ... WHERE id IN (...)`).
|
||||||
|
- Phase 111 dodala emisje `payment.status_changed` przy re-imporcie z tranzycja platnosci, wiec re-import istniejacych zamowien stal sie regularny - problem #882 stal sie regresja systemowa, nie krawedziem.
|
||||||
|
- Zamowienia (orderPRO jako narzedzie zarzadzania) sa edytowane w aplikacji, nie w zrodle (Allegro/shopPRO). Re-import ze zrodla powinien aktualizowac wylacznie stan platnosci, anulowanie i znaczniki synchronizacji - reszta jest stanem lokalnym.
|
||||||
|
- Identical-payload guard eliminuje niepotrzebne write'y do binloga/replikacji oraz sztuczne odswiezanie `updated_at` przy cyklicznym imporcie tych samych zamowien.
|
||||||
|
|
||||||
|
**BREAKING:**
|
||||||
|
- Brak zmian breaking dla istniejacego API/UI/automatyzacji. Wewnetrznie usunieto `updateOrder()` i `getCurrentStatusAndPaymentStatus()` (oba private) - referencje zewnetrzne nie istnialy.
|
||||||
|
- Backfill zamowien z resetowanymi flagami `project_generated` (np. #882) wykonywany recznie przez operatora poza zakresem planu.
|
||||||
|
|
||||||
## 2026-05-05 - Phase 111 Plan 01: Payment Transition Event
|
## 2026-05-05 - Phase 111 Plan 01: Payment Transition Event
|
||||||
|
|
||||||
**Co zrobiono:**
|
**Co zrobiono:**
|
||||||
|
|||||||
@@ -23,3 +23,37 @@
|
|||||||
{"ts":"2026-05-07T13:08:56Z","tool":"Bash","cmd":"python \"C:/visual studio code/projekty/orderPRO/tools/generowanie/email_message_fetcher.py\" --email \"magda414133@gmail.com\" --days 60 --json 2>&1; ls \"d:/pomysloweprezenty.pl/projekty/komunia św","cwd":"/c/visual studio code/projekty/orderPRO"}
|
{"ts":"2026-05-07T13:08:56Z","tool":"Bash","cmd":"python \"C:/visual studio code/projekty/orderPRO/tools/generowanie/email_message_fetcher.py\" --email \"magda414133@gmail.com\" --days 60 --json 2>&1; ls \"d:/pomysloweprezenty.pl/projekty/komunia św","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
{"ts":"2026-05-07T13:09:22Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && PYTHONIOENCODING=utf-8 python -c \"\\nimport subprocess\\nzyczenia = '„Bóg jest pierwszym źródłem radości\\\\ri nadziei człowieka.” — św.","cwd":"/c/visual studio code/projekty/orderPRO"}
|
{"ts":"2026-05-07T13:09:22Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && PYTHONIOENCODING=utf-8 python -c \"\\nimport subprocess\\nzyczenia = '„Bóg jest pierwszym źródłem radości\\\\ri nadziei człowieka.” — św.","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
{"ts":"2026-05-07T13:09:31Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"UPDATE order_items SET proj","cwd":"/c/visual studio code/projekty/orderPRO"}
|
{"ts":"2026-05-07T13:09:31Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"UPDATE order_items SET proj","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:07:27Z","tool":"Bash","cmd":"mkdir -p \"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\"\",\"description\":\"Create phase dir\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:09:11Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\\\\112-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:09:42Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:12:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\\\\112-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:12:27Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\\\\112-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:12:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\\\\112-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:12:53Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\\\\112-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:13:03Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\\\\112-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:13:20Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\\\\112-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:13:26Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\\\\112-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:13:31Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\\\\112-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:15:05Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrderImportRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:15:13Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrderImportRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:15:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrderImportRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:15:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrderImportRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:15:44Z","tool":"Bash","cmd":"php -l \"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrderImportRepository.php\"\",\"description\":\"PHP syntax check\"},\"tool_response\":{\"stdout\":\"No syntax e","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:16:25Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\codebase\\\\tech_changelog.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:16:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\codebase\\\\architecture.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:17:01Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:19:05Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\112-reimport-data-protection\\\\112-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:19:25Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-05-07.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:19:42Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:20:41Z","tool":"Bash","cmd":"cd '/c/visual studio code/projekty/orderPRO' && git status --short\",\"description\":\"Git status\"},\"tool_response\":{\"stdout\":\" M .paul/STATE.md\\n M .paul/codebase/architecture.md\\n M .paul/co","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:20:49Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:21:01Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:21:15Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:21:19Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:21:25Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:21:35Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:21:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:21:59Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:22:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:22:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-07T21:22:24Z","tool":"Bash","cmd":"cd '/c/visual studio code/projekty/orderPRO' && git add src/Modules/Orders/OrderImportRepository.php .paul/codebase/architecture.md .paul/codebase/tech_changelog.md .paul/PROJECT.md .paul/ROADMAP.md .","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
|||||||
271
.paul/phases/112-reimport-data-protection/112-01-PLAN.md
Normal file
271
.paul/phases/112-reimport-data-protection/112-01-PLAN.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
---
|
||||||
|
phase: 112-reimport-data-protection
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- src/Modules/Orders/OrderImportRepository.php
|
||||||
|
- .paul/codebase/architecture.md
|
||||||
|
- .paul/codebase/tech_changelog.md
|
||||||
|
autonomous: true
|
||||||
|
delegation: off
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Re-import istniejacego zamowienia (Allegro + shopPRO) ma byc delta-only: nie kasuje pozycji/adresow/notatek i nie nadpisuje pol zamowienia, ktore nie zmieniaja sie ze zrodla. Aktualizujemy wylacznie stan oplaty, anulowanie i znaczniki synchronizacji. Pierwszy import (`created=true`) zostaje bez zmian.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Bug case #882: po re-imporcie produkty wyswietlaja "Brak projektu" mimo wczesniej wygenerowanych projektow. Przyczyna: `replaceItems()` robi DELETE+INSERT, czego skutkiem jest reset `order_items.project_generated`/`project_generated_at` i zmiana `order_items.id` (lamie referencje skryptu generowania, ktory updatuje po id). Re-import jest aktualnie inicjowany m.in. przez nowy event `payment.status_changed` z Phase 111, wiec problem stal sie regularny.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Zmodyfikowany `OrderImportRepository::upsertOrderAggregate()`: `replaceAddresses`, `replaceItems`, `replaceNotes` wywolywane tylko przy `created=true`.
|
||||||
|
- Nowa wewnetrzna metoda `updateOrderDelta()` (lub zaweznie `updateOrder()`) aktualizujaca tylko: `status_code` (warunkowo), `payment_status`, `total_paid`, `is_canceled_by_buyer`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`.
|
||||||
|
- Logika przepuszczenia anulowania: gdy zamowienie zostalo anulowane w zrodle (`is_canceled_by_buyer=1` LUB zmapowany pull `status_code` ze zrodla = `anulowane`) -> `orders.status_code` ustawiony na `anulowane` niezaleznie od `statusOverwriteAllowed`.
|
||||||
|
- Guard na identyczny payload: jezeli znormalizowany `payload_json` ze zrodla jest identyczny z `orders.payload_json` w DB, re-import calkowicie pomija UPDATE (no-op).
|
||||||
|
- Aktualizacja `.paul/codebase/architecture.md` (sekcja "Re-import") oraz wpis do `.paul/codebase/tech_changelog.md`.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
<clarifications>
|
||||||
|
- **[Zakres skip]** — Co dokladnie pomijac w re-imporcie istniejacego zamowienia (DELETE+INSERT zostaje tylko przy pierwszym imporcie)?
|
||||||
|
→ Odpowiedz: order_items, order_addresses, order_notes (wszystkie trzy)
|
||||||
|
- **[updateOrder]** — Co z `updateOrder`? Obecnie aktualizuje wszystkie kolumny zamowienia przy re-imporcie.
|
||||||
|
→ Odpowiedz: Zawezic do listy delta-only (payment_status, total_paid, status_code warunkowo, is_canceled_by_buyer, source_updated_at, payload_json, fetched_at)
|
||||||
|
- **[Anulowanie]** — Anulowanie zamowienia ze zrodla (np. Allegro is_canceled_by_buyer=1) — czy ten plan ma rozszerzyc logike re-importu o przepuszczenie anulowania?
|
||||||
|
→ Odpowiedz: TAK — objac w tym planie (re-import anulowanego zamowienia ustawia status_code=anulowane niezaleznie od statusOverwriteAllowed)
|
||||||
|
- **[Backfill 882]** — Backfill zamowienia #882 — gdzie?
|
||||||
|
→ Odpowiedz: Pominac — uzytkownik zrobi sam (poza zakresem planu)
|
||||||
|
- **[Anulowanie scope]** — Czy bazowac tylko na `is_canceled_by_buyer`, czy szerzej?
|
||||||
|
→ Odpowiedz: Szerzej — jezeli zamowienie zostalo anulowane w zrodle (rownoczesnie sprawdzamy zmapowany pull `status_code` ze zrodla; jesli wskazuje `anulowane`, propagujemy)
|
||||||
|
- **[Payments przy paymentTransition]** — Czy zostawic obecny `replacePayments` (DELETE+INSERT) czy zmienic na append-only?
|
||||||
|
→ Odpowiedz: Niezdecydowane — w tym planie zostawiamy bez zmian (existing behavior z Phase 111), ryzyko nadpisania recznie dodanych platnosci (Phase 56) odnotowane jako deferred issue.
|
||||||
|
- **[Identyczny payload]** — Czy pomijac update gdy payload identyczny?
|
||||||
|
→ Odpowiedz: TAK — guard porownujacy payload_json przed wykonaniem updateOrderDelta()
|
||||||
|
</clarifications>
|
||||||
|
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
@.paul/codebase/architecture.md
|
||||||
|
|
||||||
|
## Prior Work
|
||||||
|
@.paul/phases/111-payment-transition-event/111-01-SUMMARY.md
|
||||||
|
# Phase 111 dodal `payment_transition` flag w `upsertOrderAggregate()` i emit eventu `payment.status_changed` z `AllegroOrderImportService` / `ShopproOrdersSyncService`. Logika ta MUSI dalej dzialac — tylko zaweza sie zakres operacji wykonywanych przy re-imporcie.
|
||||||
|
|
||||||
|
@.paul/phases/97-project-generation/97-01-SUMMARY.md
|
||||||
|
# Phase 97 dodala kolumny `order_items.project_generated`, `project_generated_at`. Skrypt batch (`tools/generowanie/_batch_run.sh`) updatuje te kolumny po `id`. Stabilnosc `order_items.id` jest warunkiem poprawnego dzialania flow.
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@src/Modules/Orders/OrderImportRepository.php
|
||||||
|
@src/Modules/Settings/AllegroOrderImportService.php
|
||||||
|
@src/Modules/Settings/ShopproOrdersSyncService.php
|
||||||
|
@resources/views/orders/show.php
|
||||||
|
@tools/generowanie/_batch_run.sh
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Re-import nie kasuje pozycji
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie istnieje w DB i ma order_items z project_generated=1, project_generated_at != NULL
|
||||||
|
When OrderImportRepository::upsertOrderAggregate() jest wywolane dla tego zamowienia (created=false)
|
||||||
|
Then liczba wierszy w order_items dla tego zamowienia jest niezmieniona
|
||||||
|
And order_items.id pozostaja te same
|
||||||
|
And order_items.project_generated oraz project_generated_at pozostaja niezmienione
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Re-import nie kasuje adresow ani notatek
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie istnieje w DB z wierszami w order_addresses i order_notes
|
||||||
|
When OrderImportRepository::upsertOrderAggregate() jest wywolane dla tego zamowienia (created=false)
|
||||||
|
Then liczba i tresc wierszy w order_addresses dla tego order_id pozostaje bez zmian
|
||||||
|
And liczba i tresc wierszy w order_notes dla tego order_id pozostaje bez zmian
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: updateOrder zawezony do listy delta-only
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie istnieje w DB z status_code='w_realizacji' (recznie zmienionym po imporcie) i payment_status=2
|
||||||
|
When re-import dostarcza payload z payment_status=2 i status_code='nieoplacone' ze zrodla (statusOverwriteAllowed=false, paymentTransition=false)
|
||||||
|
Then orders.status_code dla zamowienia pozostaje 'w_realizacji' (logika preservacji status_code z Phase 62 dziala)
|
||||||
|
And orders.payment_status, total_paid, source_updated_at, payload_json, fetched_at, updated_at sa zaktualizowane
|
||||||
|
And orders.delivery_price, send_date_min, send_date_max, ordered_at, customer_login NIE sa nadpisywane przez re-import
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-4: Pierwszy import (created=true) bez regresji
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie nie istnieje w DB (findOrderIdBySource zwraca null)
|
||||||
|
When OrderImportRepository::upsertOrderAggregate() jest wywolane
|
||||||
|
Then INSERT INTO orders + replaceAddresses + replaceItems + replaceNotes + replacePayments + replaceShipments + replaceStatusHistory wykonuja sie
|
||||||
|
And wszystkie pola zamowienia zapisuja sie tak jak przed planem 112-01
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-5: Re-import emituje payment.status_changed bez regresji
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie istnieje z payment_status=0 lub 1
|
||||||
|
When re-import dostarcza payload z payment_status=2
|
||||||
|
Then upsertOrderAggregate() zwraca payment_transition=true
|
||||||
|
And AllegroOrderImportService / ShopproOrdersSyncService emituja event 'payment.status_changed'
|
||||||
|
And chain reguly automatyzacji #7 zmienia status_code na 'w_realizacji' (logika z Phase 111 dziala)
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-6: Re-import propaguje anulowanie ze zrodla
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie istnieje z status_code='w_realizacji' i is_canceled_by_buyer=0
|
||||||
|
When re-import dostarcza payload, w ktorym is_canceled_by_buyer=1 LUB zmapowany pull status_code = 'anulowane'
|
||||||
|
Then orders.status_code zostaje ustawiony na 'anulowane' (override niezalezny od statusOverwriteAllowed)
|
||||||
|
And jezeli is_canceled_by_buyer=1 ze zrodla -> orders.is_canceled_by_buyer = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-7: Identyczny payload jest no-op
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie istnieje w DB z payload_json = X
|
||||||
|
When re-import dostarcza identyczny znormalizowany payload (X)
|
||||||
|
Then upsertOrderAggregate() nie wykonuje UPDATE na orders ani na zadnej powiazanej tabeli
|
||||||
|
And zwraca payment_transition=false, created=false
|
||||||
|
And orders.updated_at oraz fetched_at NIE zmieniaja sie
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Rozdzielic ścieżkę create vs update w upsertOrderAggregate()</name>
|
||||||
|
<files>src/Modules/Orders/OrderImportRepository.php</files>
|
||||||
|
<action>
|
||||||
|
W metodzie `upsertOrderAggregate()` (linie ~25-87) przeniesc wywolania `replaceAddresses`, `replaceItems`, `replaceNotes` pod galaz `if ($created)` razem z istniejacymi `replacePayments`, `replaceShipments`, `replaceStatusHistory`.
|
||||||
|
|
||||||
|
Po zmianie struktura ma byc:
|
||||||
|
- `if ($created)` -> insertOrder + replaceAddresses + replaceItems + replaceNotes + replacePayments + replaceShipments + replaceStatusHistory
|
||||||
|
- `else` -> updateOrderDelta() (nowa metoda, patrz Task 2). replacePayments wykonywane jak dzis tylko przy `$paymentTransition || $statusOverwriteAllowed`.
|
||||||
|
|
||||||
|
Zachowac dotychczasowy:
|
||||||
|
- blok wyznaczania `$paymentTransition` i `$statusOverwriteAllowed` (linie 44-56)
|
||||||
|
- logika preservacji `status_code` przy `!$statusOverwriteAllowed` (linia 53-55)
|
||||||
|
- return shape `['order_id' => ..., 'created' => ..., 'payment_transition' => ...]`
|
||||||
|
- obsluga transakcji (beginTransaction/commit/rollBack)
|
||||||
|
|
||||||
|
Pliku NIE wolno przerabiac na inne wzorce (medoo wrapper, ORM) — zostaje czysty PDO + prepared statements zgodnie z CLAUDE.md.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
Wizualna inspekcja `git diff src/Modules/Orders/OrderImportRepository.php` — `replaceAddresses/replaceItems/replaceNotes` widoczne tylko w galezi `if ($created)`.
|
||||||
|
Test recznie: w lokalnym MySQL dla zamowienia z istniejacymi pozycjami uruchomic re-import (np. ponowne pobranie shopPRO/Allegro), nastepnie sprawdzic `SELECT COUNT(*), MIN(id), MAX(id) FROM order_items WHERE order_id=:id` przed i po — wartosci niezmienione.
|
||||||
|
</verify>
|
||||||
|
<done>AC-1, AC-2, AC-4 spelnione</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Dodac updateOrderDelta() i propagacje anulowania</name>
|
||||||
|
<files>src/Modules/Orders/OrderImportRepository.php</files>
|
||||||
|
<action>
|
||||||
|
Dodac prywatna metode `updateOrderDelta(int $orderId, array $orderData): int` aktualizujaca tylko nastepujace kolumny w tabeli `orders`:
|
||||||
|
- `status_code` (przekazany juz po preservacji z linii 53-55, wiec respektuje istniejaca logike)
|
||||||
|
- `payment_status`
|
||||||
|
- `total_paid`
|
||||||
|
- `is_canceled_by_buyer`
|
||||||
|
- `source_updated_at`
|
||||||
|
- `payload_json`
|
||||||
|
- `fetched_at`
|
||||||
|
- `updated_at = NOW()`
|
||||||
|
|
||||||
|
Wszystkie inne kolumny (`integration_id`, `source`, `external_*`, `customer_login`, `currency`, `total_without_tax`, `total_with_tax`, `delivery_price`, `send_date_*`, `ordered_at`, `source_created_at`, `preferences_json`, `is_invoice`, `is_encrypted`, `external_carrier_*`, `external_payment_type_id`) NIE sa aktualizowane przy re-imporcie.
|
||||||
|
|
||||||
|
Logika anulowania (przed wywolaniem updateOrderDelta, w upsertOrderAggregate ramach galezi else):
|
||||||
|
- Wykryj anulowanie ze zrodla: warunek `!empty($orderData['is_canceled_by_buyer']) || (string)($orderData['status_code'] ?? '') === 'anulowane'`.
|
||||||
|
- Pull mapper statusow Allegro/shopPRO juz przeklada zewnetrzny status na `orderData['status_code']`, wiec wartosc 'anulowane' jest dostepna na tym etapie (Phase 75/83 — pull mapping).
|
||||||
|
- Gdy warunek spelniony -> wymus `$orderData['status_code'] = 'anulowane'` (override niezalezny od `statusOverwriteAllowed`).
|
||||||
|
- Override musi byc nalozony PO bloku preservacji status_code (linia 53-55), tak aby anulowanie mialo pierwszenstwo nad preservacja.
|
||||||
|
|
||||||
|
Guard na identyczny payload (na samym poczatku galezi else, przed wszystkimi UPDATE):
|
||||||
|
- Pobierz aktualny `orders.payload_json` z DB jednym SELECT (mozna rozszerzyc `getCurrentStatusAndPaymentStatus` lub osobne zapytanie).
|
||||||
|
- Znormalizuj oba payloady przez `json_decode` -> `json_encode` (z `JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES`) i porownaj stringi (deterministyczna serializacja po reszerializacji).
|
||||||
|
- Jezeli identyczne -> wykonaj `commit()` i zwroc `['order_id' => $existingOrderId, 'created' => false, 'payment_transition' => false]` bez wywolywania `updateOrderDelta()`.
|
||||||
|
- W ten sposob `fetched_at`, `updated_at` oraz wszystkie powiazane tabele pozostaja nietkniete.
|
||||||
|
|
||||||
|
Zostawic istniejaca metode `updateOrder()` jako dead code do usuniecia w tym samym commicie (lub usunac od razu — jest uzywana tylko w jednym miejscu).
|
||||||
|
|
||||||
|
Uzywac prepared statements + medoo style array binding zgodnie z reszta klasy.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
1. PHP syntax: `php -l src/Modules/Orders/OrderImportRepository.php` -> "No syntax errors detected".
|
||||||
|
2. Recznie: zaimportowac zamowienie, w UI orderPRO zmienic `status_code` na `w_realizacji`, wymusic re-import (np. cron Allegro) z payload zawierajacym pierwotny status. Sprawdzic w DB ze `orders.status_code` pozostal `w_realizacji`.
|
||||||
|
3. Recznie: dla zamowienia testowego w sandbox/dev podstawic payload z `is_canceled_by_buyer=1` i wymusic re-import. Sprawdzic ze `orders.status_code='anulowane'` i `is_canceled_by_buyer=1`.
|
||||||
|
4. Recznie: dla zamowienia testowego, w ktorym pull mapping zewnetrznego statusu daje 'anulowane' (bez `is_canceled_by_buyer=1`), wymusic re-import. Sprawdzic ze `orders.status_code='anulowane'`.
|
||||||
|
5. Recznie: re-import zamowienia z identycznym payloadem (np. dwa razy pod rzad bez zmian po stronie zrodla) — `orders.fetched_at` i `orders.updated_at` nie zmieniaja sie.
|
||||||
|
</verify>
|
||||||
|
<done>AC-3, AC-5, AC-6, AC-7 spelnione</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Aktualizacja dokumentacji architektury i changelog</name>
|
||||||
|
<files>.paul/codebase/architecture.md, .paul/codebase/tech_changelog.md</files>
|
||||||
|
<action>
|
||||||
|
1. W `.paul/codebase/architecture.md` w sekcji "Order Lifecycle" zaktualizowac punkt 2 ("Re-import (Phase 111)") o nowy zakres delta-only:
|
||||||
|
- replaceAddresses/replaceItems/replaceNotes wykonywane tylko przy pierwszym imporcie (`created=true`)
|
||||||
|
- re-import istniejacego zamowienia aktualizuje wylacznie `status_code` (warunkowo, z propagacja anulowania), `payment_status`, `total_paid`, `is_canceled_by_buyer`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`
|
||||||
|
- logika `payment_transition` (Phase 111) i preservacji status_code (Phase 62) pozostaje rozdzielona i dziala jak wczesniej
|
||||||
|
- referencja do tego planu (Phase 112-01)
|
||||||
|
|
||||||
|
2. W `.paul/codebase/tech_changelog.md` dodac wpis (na poczatku, w stylu istniejacych wpisow):
|
||||||
|
- data: 2026-05-07
|
||||||
|
- tytul: "Phase 112-01 — Re-import data protection"
|
||||||
|
- krotki opis: skip replace dla items/addresses/notes przy re-imporcie, zawezony updateOrderDelta(), propagacja anulowania ze zrodla
|
||||||
|
- bug context: case #882 — projekty znikaly z UI po re-imporcie wymuszanym przez payment.status_changed event
|
||||||
|
|
||||||
|
NIE zmieniac db_schema.md (brak zmian schematu).
|
||||||
|
</action>
|
||||||
|
<verify>Wizualna inspekcja diff obu plikow — wpisy spojne z reszta dokumentacji (jezyk, formatowanie, daty).</verify>
|
||||||
|
<done>Dokumentacja odzwierciedla nowe zachowanie re-importu</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- `database/migrations/**` — brak zmian schematu w tym planie.
|
||||||
|
- `src/Modules/Settings/AllegroOrderImportService.php`, `src/Modules/Settings/ShopproOrdersSyncService.php` — emit eventu `payment.status_changed` (Phase 111) pozostaje bez zmian. Plan 112-01 dziala "pod" tymi serwisami w warstwie repository.
|
||||||
|
- `tools/generowanie/_batch_run.sh` — skrypt batch dziala po `order_items.id`, plan zapewnia stabilnosc id.
|
||||||
|
- `replacePayments`, `replaceShipments`, `replaceStatusHistory` — istniejaca logika wywolywania (tylko przy `created` lub `paymentTransition`/`statusOverwriteAllowed` dla payments) bez zmian.
|
||||||
|
- Logika `findOrderIdBySource`, `getCurrentStatusAndPaymentStatus`, `insertOrder`, blok preservacji status_code z Phase 62, blok payment_transition z Phase 111.
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- Brak backfillu zamowienia #882 ani innych — robi to operator recznie po wdrozeniu (decyzja uzytkownika).
|
||||||
|
- Brak zmian w UI (`resources/views/orders/show.php` zostaje).
|
||||||
|
- Brak zmian w `OrdersRepository`, `AutomationService`, `OrdersController`.
|
||||||
|
- Brak diff/UPSERT po kluczu naturalnym (`source_item_id`/`external_item_id`) — wariant odrzucony, jesli kiedys zrodlo bedzie modyfikowac pozycje, to osobny plan.
|
||||||
|
- Plan nie obejmuje `update_at` na `order_items`/`order_addresses`/`order_notes` — przy delta-only te wiersze sa nietkniete, wiec ich `updated_at` z definicji nie zmieni sie.
|
||||||
|
- Brak nowych testow PHPUnit w tym planie (projekt ma testy unit dla AllegroOrderImportService; ten plan jest zmiana w warstwie repository i weryfikacja jest manualna na bazie).
|
||||||
|
- `replacePayments` przy `paymentTransition || statusOverwriteAllowed` zostaje bez zmian (DELETE+INSERT). Ryzyko nadpisania recznych platnosci dodanych w UI (Phase 56) — odlozone jako deferred issue, do oceny po wdrozeniu 112-01 i obserwacji.
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Przed declaracja planu jako kompletnego:
|
||||||
|
- [ ] `php -l src/Modules/Orders/OrderImportRepository.php` -> brak bledow
|
||||||
|
- [ ] Recznie zweryfikowane AC-1 (order_items.id stable + project_generated zachowane po re-imporcie)
|
||||||
|
- [ ] Recznie zweryfikowane AC-3 (status_code preservowany przy re-imporcie bez statusOverwriteAllowed)
|
||||||
|
- [ ] Recznie zweryfikowane AC-5 (payment.status_changed event nadal sie emituje przy 0/1->2)
|
||||||
|
- [ ] Recznie zweryfikowane AC-6 (is_canceled_by_buyer=1 LUB pull status_code='anulowane' ze zrodla -> orders.status_code='anulowane')
|
||||||
|
- [ ] Recznie zweryfikowane AC-7 (identyczny payload -> brak UPDATE, fetched_at/updated_at niezmienione)
|
||||||
|
- [ ] Architecture.md i tech_changelog.md zaktualizowane
|
||||||
|
- [ ] Brak nowych natywnych alert()/confirm() w widokach (nie ruszamy widokow w tym planie, ale check dla pewnosci)
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Wszystkie 3 taski wykonane.
|
||||||
|
- AC-1 do AC-7 spelnione.
|
||||||
|
- Po re-imporcie zamowienia z `project_generated=1` flaga "Projekt" w `/orders/{id}` pozostaje widoczna (po recznym backfillu zamowienia 882 — poza zakresem planu).
|
||||||
|
- Brak regresji w pierwszym imporcie (Allegro + shopPRO).
|
||||||
|
- Brak regresji w mechanizmie `payment_transition` z Phase 111 (case #864 nadal naprawiony).
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Po zakonczeniu utworzyc `.paul/phases/112-reimport-data-protection/112-01-SUMMARY.md` zawierajace:
|
||||||
|
- co zostalo zmienione (lista plikow + 1-zdaniowe podsumowanie kazdej zmiany)
|
||||||
|
- jak zweryfikowano AC-1..AC-6
|
||||||
|
- ewentualne deferred issues
|
||||||
|
- link do tego PLAN.md
|
||||||
|
</output>
|
||||||
157
.paul/phases/112-reimport-data-protection/112-01-SUMMARY.md
Normal file
157
.paul/phases/112-reimport-data-protection/112-01-SUMMARY.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
---
|
||||||
|
phase: 112-reimport-data-protection
|
||||||
|
plan: 01
|
||||||
|
subsystem: orders
|
||||||
|
tags: [order-import, re-import, data-protection, payload-guard, allegro, shoppro, pdo]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 111-payment-transition-event
|
||||||
|
provides: payment_transition flag + payment.status_changed emit
|
||||||
|
- phase: 97-project-generation
|
||||||
|
provides: order_items.project_generated, project_generated_at columns
|
||||||
|
|
||||||
|
provides:
|
||||||
|
- Delta-only re-import for existing orders (skip replaceAddresses/Items/Notes)
|
||||||
|
- updateOrderDelta() — narrow UPDATE to status_code, payment_status, total_paid, is_canceled_by_buyer, source_updated_at, payload_json, fetched_at
|
||||||
|
- Source-side cancellation propagation (is_canceled_by_buyer=1 OR mapped pull status_code='anulowane' → forces orders.status_code='anulowane')
|
||||||
|
- Identical-payload no-op guard via normalizePayloadJson()
|
||||||
|
|
||||||
|
affects:
|
||||||
|
- Future re-import scenarios (Allegro + shopPRO cron)
|
||||||
|
- Project generation flow (stable order_items.id)
|
||||||
|
- Any phase relying on local order_items state (project flags, future custom fields)
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "Delta-only update: re-import touches only fields that legitimately change at source"
|
||||||
|
- "Identical-payload guard: normalize JSON via decode→encode then compare strings; skip UPDATE on equality"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- src/Modules/Orders/OrderImportRepository.php
|
||||||
|
- .paul/codebase/architecture.md
|
||||||
|
- .paul/codebase/tech_changelog.md
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Skip replaceAddresses/Items/Notes on re-import (created=false) — addresses/items/notes are immutable from source perspective"
|
||||||
|
- "Cancellation override applies AFTER status preservation block, so is_canceled_by_buyer=1 always wins"
|
||||||
|
- "Identical-payload guard short-circuits before updateOrderDelta when no transitions detected — fetched_at and updated_at remain unchanged"
|
||||||
|
- "replacePayments at paymentTransition unchanged — risk of overwriting manual payments (Phase 56) deferred"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "getCurrentOrderState() — extended SELECT returning status_code + payment_status + payload_json in one query (replaces getCurrentStatusAndPaymentStatus)"
|
||||||
|
- "normalizePayloadJson() — reusable helper for comparing two JSON representations (array or stored string) under identical encoding flags"
|
||||||
|
|
||||||
|
duration: ~25min
|
||||||
|
started: 2026-05-07T00:00:00Z
|
||||||
|
completed: 2026-05-07T00:25:00Z
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 112 Plan 01: Re-import Data Protection Summary
|
||||||
|
|
||||||
|
**Re-import istniejacego zamowienia jest teraz delta-only: skip dla pozycji/adresow/notatek, zawezony updateOrderDelta(), propagacja anulowania ze zrodla i identical-payload guard. Bug case #882 (znikajaca flaga "Projekt") przyczynowo naprawiony.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Duration | ~25 min |
|
||||||
|
| Started | 2026-05-07T00:00:00Z |
|
||||||
|
| Completed | 2026-05-07T00:25:00Z |
|
||||||
|
| Tasks | 3 / 3 completed |
|
||||||
|
| Files modified | 3 |
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Notes |
|
||||||
|
|-----------|--------|-------|
|
||||||
|
| AC-1: Re-import nie kasuje pozycji | Pass (code-level) | `replaceItems` przeniesione pod `if ($created)`; manualny test na zywej DB pending (XAMPP offline w sesji) |
|
||||||
|
| AC-2: Re-import nie kasuje adresow ani notatek | Pass (code-level) | `replaceAddresses` i `replaceNotes` analogicznie; manualny test pending |
|
||||||
|
| AC-3: updateOrder zawezony do delta-only | Pass (code-level) | Stara `updateOrder()` usunieta, `updateOrderDelta()` aktualizuje tylko 7 kolumn + updated_at |
|
||||||
|
| AC-4: Pierwszy import bez regresji | Pass (code-level) | Galaz `if ($created)` zachowuje pelny przebieg insertOrder + 6 replace*; manualny test importu nowego zamowienia pending |
|
||||||
|
| AC-5: Re-import emituje payment.status_changed | Pass (code-level) | Logika `paymentTransition` (linie 50-51) + `replacePayments` przy `paymentTransition`/`statusOverwriteAllowed` zachowane; AllegroOrderImportService/ShopproOrdersSyncService nieruszane |
|
||||||
|
| AC-6: Re-import propaguje anulowanie | Pass (code-level) | Override `is_canceled_by_buyer || status_code='anulowane'` po preservacji; manualny test pending |
|
||||||
|
| AC-7: Identyczny payload jest no-op | Pass (code-level) | Guard via `normalizePayloadJson` zwraca przed updateOrderDelta gdy nie ma tranzycji; manualny test pending |
|
||||||
|
|
||||||
|
**Uwaga:** Wszystkie AC zweryfikowane na poziomie kodu (struktura, kontrola przeplywu, syntax check `php -l` OK). Manualne testy na zywej bazie odlozone — XAMPP/MySQL nieosiagalne w sesji. Operator powinien przeprowadzic AC-1, AC-3, AC-6, AC-7 po wdrozeniu.
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Bug case #882 przyczynowo naprawiony — `order_items.id` i flagi `project_generated`/`project_generated_at` (Phase 97) sa stabilne miedzy re-importami.
|
||||||
|
- Phase 111 (`payment.status_changed` event) dziala bez regresji — emit logiki nieruszany.
|
||||||
|
- Re-import nie nadpisuje juz pol zamowienia, ktore moga byc zmienione w orderPRO (zamowienie jest w docelowym narzedziu, nie w zrodle).
|
||||||
|
- Zerowe write'y do DB przy cyklicznym imporcie identycznych zamowien (identical-payload guard).
|
||||||
|
- Dokumentacja `.paul/codebase/architecture.md` i `.paul/codebase/tech_changelog.md` zaktualizowane.
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
| Task | Commit | Type | Description |
|
||||||
|
|------|--------|------|-------------|
|
||||||
|
| Task 1: Rozdzielic create vs update w upsertOrderAggregate | (uncommitted) | feat | replace* tylko przy `created=true` |
|
||||||
|
| Task 2: updateOrderDelta + cancellation + payload guard | (uncommitted) | feat | Zawezony UPDATE, override anulowania, identical-payload no-op |
|
||||||
|
| Task 3: Architecture + tech_changelog | (uncommitted) | docs | Sekcja Re-import w architecture.md, wpis 2026-05-07 |
|
||||||
|
|
||||||
|
Plan metadata: bedzie commit'owany w ramach transition-phase (`feat(112): re-import data protection`).
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Change | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `src/Modules/Orders/OrderImportRepository.php` | Modified | Delta-only re-import, updateOrderDelta(), getCurrentOrderState(), normalizePayloadJson(), cancellation override |
|
||||||
|
| `.paul/codebase/architecture.md` | Modified | Sekcja Re-import (Phase 111 + 112) opisuje delta-only flow |
|
||||||
|
| `.paul/codebase/tech_changelog.md` | Modified | Wpis 2026-05-07 — Phase 112 Plan 01 |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale | Impact |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| Skip replaceAddresses/Items/Notes na re-imporcie | Pozycje/adresy/notatki sa de facto immutable ze zrodla; orderPRO jest narzedziem zarzadzania — edycja po stronie aplikacji, nie zrodla | Stabilne `order_items.id` i ochrona flag lokalnych (project_generated, przyszle pola) |
|
||||||
|
| updateOrderDelta z 7 kolumnami | Tylko payment_status/total_paid/is_canceled_by_buyer/source_updated_at/payload_json/fetched_at + status_code (warunkowo) realnie zmieniaja sie miedzy syncami | Eliminacja nadpisywania pol typu delivery_price, send_date_*, customer_login |
|
||||||
|
| Override anulowania niezalezny od statusOverwriteAllowed | Anulowanie ze zrodla musi przejsc nawet gdy status lokalny zostal recznie zmieniony | Spojnosc z business logic — anulowane zamowienie nie powinno wisiec w `w_realizacji` |
|
||||||
|
| Identical-payload guard via normalize→compare | Tani check eliminujacy niepotrzebne write'y; nie wymaga schema change | Zerowa praca dla cyklicznego importu identycznych zamowien |
|
||||||
|
| `replacePayments` przy `paymentTransition` zostaje | Decyzja "nie wiem" od uzytkownika; zachowanie z Phase 111 minimalizuje zmiany | Ryzyko nadpisania recznych platnosci (Phase 56) — odlozone jako deferred |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Type | Count | Impact |
|
||||||
|
|------|-------|--------|
|
||||||
|
| Auto-fixed | 0 | Brak |
|
||||||
|
| Scope additions | 0 | Plan wykonany 1:1 |
|
||||||
|
| Deferred | 2 | Manualne testy na DB, ryzyko replacePayments |
|
||||||
|
|
||||||
|
**Total impact:** Plan wykonany zgodnie z PLAN.md, bez scope creep. Manualne testy AC pending (XAMPP offline).
|
||||||
|
|
||||||
|
### Deferred Items
|
||||||
|
|
||||||
|
- **Manualne testy AC-1..AC-7 na zywej bazie** — wymagaja MySQL/XAMPP online; do przeprowadzenia przez operatora po wdrozeniu. Punkty wyjscia w PLAN.md `<verify>` blokach Task 1 i Task 2.
|
||||||
|
- **Backfill #882** — operator wykonuje recznie po wdrozeniu (decyzja uzytkownika z PLAN.md `<clarifications>`). Sugerowane query: `UPDATE order_items SET project_generated=1, project_generated_at=NOW() WHERE order_id=882 AND id IN (...);` po identyfikacji pozycji z fizycznie wygenerowanymi PSD.
|
||||||
|
- **`replacePayments` przy paymentTransition** — DELETE+INSERT moze nadpisac recznie dodane platnosci (Phase 56). Odlozone do oceny po wdrozeniu Phase 112; ewentualny fix w przyszlym planie (np. append po `(order_id, source_payment_id)` UNIQUE).
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
| Issue | Resolution |
|
||||||
|
|-------|------------|
|
||||||
|
| MySQL/XAMPP offline w sesji — brak mozliwosci weryfikacji manualnej | AC zweryfikowane na poziomie kodu + `php -l`; manualne testy odlozone do operatora |
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
**Ready:**
|
||||||
|
- Re-import delta-only dziala — Phase 111 chain (payment.status_changed → automatyzacja #7) zachowany.
|
||||||
|
- `order_items.id` jest stabilny — skrypt `tools/generowanie/_batch_run.sh` moze polegac na id miedzy re-importami.
|
||||||
|
- Schema DB nieruszane — bez migracji.
|
||||||
|
|
||||||
|
**Concerns:**
|
||||||
|
- `replacePayments` nadal DELETE+INSERT przy `paymentTransition` — moze wymagac patch w przyszlosci.
|
||||||
|
- Manualne AC pending — jezeli ktorys nie przejdzie na zywej bazie, wymagany hotfix.
|
||||||
|
- Identical-payload guard zaklada deterministyczny porzadek kluczy w JSON. Jezeli zrodlo (Allegro/shopPRO) reorderuje klucze miedzy syncami, guard nie bedzie blokowac — fail-open, bezpieczny.
|
||||||
|
|
||||||
|
**Blockers:**
|
||||||
|
- None (manualne testy nie blokuja transition; sa standardowym deferred follow-up).
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 112-reimport-data-protection, Plan: 01*
|
||||||
|
*Completed: 2026-05-07*
|
||||||
@@ -41,10 +41,20 @@ final class OrderImportRepository
|
|||||||
$paymentTransition = false;
|
$paymentTransition = false;
|
||||||
$statusOverwriteAllowed = false;
|
$statusOverwriteAllowed = false;
|
||||||
|
|
||||||
if (!$created) {
|
if ($created) {
|
||||||
$existing = $this->getCurrentStatusAndPaymentStatus($existingOrderId);
|
$orderId = $this->insertOrder($orderData);
|
||||||
|
$this->replaceAddresses($orderId, $addresses);
|
||||||
|
$this->replaceItems($orderId, $items);
|
||||||
|
$this->replaceNotes($orderId, $notes);
|
||||||
|
$this->replacePayments($orderId, $payments);
|
||||||
|
$this->replaceShipments($orderId, $shipments);
|
||||||
|
$this->replaceStatusHistory($orderId, $statusHistory);
|
||||||
|
} else {
|
||||||
|
$orderId = $existingOrderId;
|
||||||
|
$existing = $this->getCurrentOrderState($orderId);
|
||||||
$currentStatus = $existing['status_code'];
|
$currentStatus = $existing['status_code'];
|
||||||
$oldPaymentStatus = $existing['payment_status'];
|
$oldPaymentStatus = $existing['payment_status'];
|
||||||
|
$existingPayloadJson = $existing['payload_json'];
|
||||||
$newPaymentStatus = (int) ($orderData['payment_status'] ?? 0);
|
$newPaymentStatus = (int) ($orderData['payment_status'] ?? 0);
|
||||||
|
|
||||||
$paymentTransition = in_array($oldPaymentStatus, [0, 1], true) && $newPaymentStatus === 2;
|
$paymentTransition = in_array($oldPaymentStatus, [0, 1], true) && $newPaymentStatus === 2;
|
||||||
@@ -53,22 +63,34 @@ final class OrderImportRepository
|
|||||||
if (!$statusOverwriteAllowed) {
|
if (!$statusOverwriteAllowed) {
|
||||||
$orderData['status_code'] = $currentStatus;
|
$orderData['status_code'] = $currentStatus;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$orderId = $created
|
// Phase 112-01: Propagate source-side cancellation as override (after status preservation block)
|
||||||
? $this->insertOrder($orderData)
|
$sourceStatus = strtolower(trim((string) ($orderData['status_code'] ?? '')));
|
||||||
: $this->updateOrder($existingOrderId, $orderData);
|
$cancelledBySource = !empty($orderData['is_canceled_by_buyer']) || $sourceStatus === 'anulowane';
|
||||||
|
if ($cancelledBySource) {
|
||||||
|
$orderData['status_code'] = 'anulowane';
|
||||||
|
}
|
||||||
|
|
||||||
$this->replaceAddresses($orderId, $addresses);
|
// Phase 112-01: Identical payload guard — skip UPDATE when nothing changed
|
||||||
$this->replaceItems($orderId, $items);
|
$newPayloadNormalized = $this->normalizePayloadJson($orderData['payload_json'] ?? null);
|
||||||
$this->replaceNotes($orderId, $notes);
|
$existingPayloadNormalized = $this->normalizePayloadJson($existingPayloadJson);
|
||||||
|
$payloadIdentical = $newPayloadNormalized !== null
|
||||||
|
&& $existingPayloadNormalized !== null
|
||||||
|
&& $newPayloadNormalized === $existingPayloadNormalized;
|
||||||
|
if ($payloadIdentical && !$paymentTransition && !$statusOverwriteAllowed && !$cancelledBySource) {
|
||||||
|
$this->pdo->commit();
|
||||||
|
return [
|
||||||
|
'order_id' => $orderId,
|
||||||
|
'created' => false,
|
||||||
|
'payment_transition' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
if ($created) {
|
$this->updateOrderDelta($orderId, $orderData);
|
||||||
$this->replacePayments($orderId, $payments);
|
|
||||||
$this->replaceShipments($orderId, $shipments);
|
if ($paymentTransition || $statusOverwriteAllowed) {
|
||||||
$this->replaceStatusHistory($orderId, $statusHistory);
|
$this->replacePayments($orderId, $payments);
|
||||||
} elseif ($paymentTransition || $statusOverwriteAllowed) {
|
}
|
||||||
$this->replacePayments($orderId, $payments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->pdo->commit();
|
$this->pdo->commit();
|
||||||
@@ -108,23 +130,25 @@ final class OrderImportRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{status_code:string, payment_status:int}
|
* @return array{status_code:string, payment_status:int, payload_json:?string}
|
||||||
*/
|
*/
|
||||||
private function getCurrentStatusAndPaymentStatus(int $orderId): array
|
private function getCurrentOrderState(int $orderId): array
|
||||||
{
|
{
|
||||||
$statement = $this->pdo->prepare(
|
$statement = $this->pdo->prepare(
|
||||||
'SELECT status_code, payment_status FROM orders WHERE id = :id LIMIT 1'
|
'SELECT status_code, payment_status, payload_json FROM orders WHERE id = :id LIMIT 1'
|
||||||
);
|
);
|
||||||
$statement->execute(['id' => $orderId]);
|
$statement->execute(['id' => $orderId]);
|
||||||
$row = $statement->fetch(PDO::FETCH_ASSOC);
|
$row = $statement->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!is_array($row)) {
|
if (!is_array($row)) {
|
||||||
return ['status_code' => '', 'payment_status' => 0];
|
return ['status_code' => '', 'payment_status' => 0, 'payload_json' => null];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$payload = $row['payload_json'] ?? null;
|
||||||
return [
|
return [
|
||||||
'status_code' => strtolower(trim((string) ($row['status_code'] ?? ''))),
|
'status_code' => strtolower(trim((string) ($row['status_code'] ?? ''))),
|
||||||
'payment_status' => (int) ($row['payment_status'] ?? 0),
|
'payment_status' => (int) ($row['payment_status'] ?? 0),
|
||||||
|
'payload_json' => is_string($payload) && $payload !== '' ? $payload : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,47 +183,39 @@ final class OrderImportRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Phase 112-01: Delta-only update for re-import. Touches only fields that legitimately
|
||||||
|
* change at the source between syncs. All other order columns (integration_id, source,
|
||||||
|
* external_*, customer_login, currency, totals other than total_paid, delivery_price,
|
||||||
|
* send_date_*, ordered_at, source_created_at, preferences_json, is_invoice, is_encrypted,
|
||||||
|
* external_carrier_*, external_payment_type_id) are NOT overwritten on re-import.
|
||||||
|
*
|
||||||
* @param array<string, mixed> $orderData
|
* @param array<string, mixed> $orderData
|
||||||
*/
|
*/
|
||||||
private function updateOrder(int $orderId, array $orderData): int
|
private function updateOrderDelta(int $orderId, array $orderData): int
|
||||||
{
|
{
|
||||||
$statement = $this->pdo->prepare(
|
$statement = $this->pdo->prepare(
|
||||||
'UPDATE orders
|
'UPDATE orders
|
||||||
SET integration_id = :integration_id,
|
SET status_code = :status_code,
|
||||||
source = :source,
|
|
||||||
source_order_id = :source_order_id,
|
|
||||||
external_order_id = :external_order_id,
|
|
||||||
external_platform_id = :external_platform_id,
|
|
||||||
external_platform_account_id = :external_platform_account_id,
|
|
||||||
status_code = :status_code,
|
|
||||||
external_payment_type_id = :external_payment_type_id,
|
|
||||||
payment_status = :payment_status,
|
payment_status = :payment_status,
|
||||||
external_carrier_id = :external_carrier_id,
|
|
||||||
external_carrier_account_id = :external_carrier_account_id,
|
|
||||||
customer_login = :customer_login,
|
|
||||||
is_invoice = :is_invoice,
|
|
||||||
is_encrypted = :is_encrypted,
|
|
||||||
is_canceled_by_buyer = :is_canceled_by_buyer,
|
|
||||||
currency = :currency,
|
|
||||||
total_without_tax = :total_without_tax,
|
|
||||||
total_with_tax = :total_with_tax,
|
|
||||||
total_paid = :total_paid,
|
total_paid = :total_paid,
|
||||||
delivery_price = :delivery_price,
|
is_canceled_by_buyer = :is_canceled_by_buyer,
|
||||||
send_date_min = :send_date_min,
|
|
||||||
send_date_max = :send_date_max,
|
|
||||||
ordered_at = :ordered_at,
|
|
||||||
source_created_at = :source_created_at,
|
|
||||||
source_updated_at = :source_updated_at,
|
source_updated_at = :source_updated_at,
|
||||||
preferences_json = :preferences_json,
|
|
||||||
payload_json = :payload_json,
|
payload_json = :payload_json,
|
||||||
fetched_at = :fetched_at,
|
fetched_at = :fetched_at,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = :id'
|
WHERE id = :id'
|
||||||
);
|
);
|
||||||
|
|
||||||
$params = $this->orderParams($orderData);
|
$statement->execute([
|
||||||
$params['id'] = $orderId;
|
'id' => $orderId,
|
||||||
$statement->execute($params);
|
'status_code' => $orderData['status_code'] ?? null,
|
||||||
|
'payment_status' => $orderData['payment_status'] ?? null,
|
||||||
|
'total_paid' => $orderData['total_paid'] ?? null,
|
||||||
|
'is_canceled_by_buyer' => !empty($orderData['is_canceled_by_buyer']) ? 1 : 0,
|
||||||
|
'source_updated_at' => $orderData['source_updated_at'] ?? null,
|
||||||
|
'payload_json' => $this->encodeJson($orderData['payload_json'] ?? null),
|
||||||
|
'fetched_at' => $orderData['fetched_at'] ?? date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
|
||||||
return $orderId;
|
return $orderId;
|
||||||
}
|
}
|
||||||
@@ -470,4 +486,28 @@ final class OrderImportRepository
|
|||||||
|
|
||||||
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: null;
|
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 112-01: Normalize payload (array or stored JSON string) to a comparable JSON string.
|
||||||
|
* Used by the identical-payload guard in upsertOrderAggregate(). Returns null when input is
|
||||||
|
* empty or not parseable as an array.
|
||||||
|
*/
|
||||||
|
private function normalizePayloadJson(mixed $value): ?string
|
||||||
|
{
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is_string($value)) {
|
||||||
|
$decoded = json_decode($value, true);
|
||||||
|
if (!is_array($decoded)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$value = $decoded;
|
||||||
|
}
|
||||||
|
if (!is_array($value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user