update
This commit is contained in:
@@ -69,6 +69,8 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
||||
- [x] Zachowanie danych formularza automatyzacji po bledzie walidacji — Phase 58
|
||||
- [x] Automatyzacja: event `order.status_changed` + warunek `order_status` — Phase 59
|
||||
- [x] Automatyzacja: event `order.status_aged` (cron) + warunek `days_in_status` — Phase 60
|
||||
- [x] Aktywacja przycisku Platnosc w headerze zamowienia + poprawa odstepu w formularzu platnosci — Phase 61
|
||||
- [x] Ochrona danych lokalnych przy re-imporcie + rozroznienie import/aktualizacja w activity log shopPRO — Phase 62
|
||||
|
||||
### Active (In Progress)
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel
|
||||
| 58 | Automation Form Preserve | 1/1 | Complete |
|
||||
| 59 | Order Status Automation Event | 1/1 | Complete |
|
||||
| 60 | Order Status Aged Event | 1/1 | Complete |
|
||||
| 61 | Payment Button Activation | 1/1 | Complete |
|
||||
| 62 | Import Re-import Safety | 1/1 | Complete |
|
||||
| TBD | Mobile Orders List | - | Not started |
|
||||
| TBD | Mobile Order Details | - | Not started |
|
||||
| TBD | Mobile Settings | - | Not started |
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
See: .paul/PROJECT.md (updated 2026-03-31)
|
||||
|
||||
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
|
||||
**Current focus:** Milestone v3.0 — Phase 60 complete, ready for next PLAN
|
||||
**Current focus:** Milestone v3.0 — Phase 62 complete, ready for next PLAN
|
||||
|
||||
## Current Position
|
||||
|
||||
Milestone: v3.0 Mobile Responsive — In progress
|
||||
Phase: 9 of N (60 - Order Status Aged Event) — Complete
|
||||
Plan: 60-01 complete
|
||||
Status: Loop complete — phase 60 done, ready for next PLAN
|
||||
Last activity: 2026-03-31 — UNIFY closed for 60-01
|
||||
Phase: 11 of N (62 - Import Re-import Safety) — Complete
|
||||
Plan: 62-01 complete
|
||||
Status: Loop complete — phase 62 done, ready for next PLAN
|
||||
Last activity: 2026-03-31 — UNIFY closed for 62-01
|
||||
|
||||
Progress:
|
||||
- Milestone: [######░░░░] ~55%
|
||||
- Phase 60: [##########] 100%
|
||||
- Milestone: [######░░░░] ~58%
|
||||
- Phase 62: [##########] 100%
|
||||
|
||||
## Loop Position
|
||||
|
||||
@@ -30,12 +30,12 @@ PLAN ──▶ APPLY ──▶ UNIFY
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-03-31
|
||||
Stopped at: Phase 60 complete
|
||||
Stopped at: Phase 62 complete
|
||||
Next action: /paul:plan dla kolejnego modulu
|
||||
Resume file: .paul/phases/60-order-status-aged-event/60-01-SUMMARY.md
|
||||
Resume file: .paul/phases/62-import-reimport-safety/62-01-SUMMARY.md
|
||||
|
||||
## Git State
|
||||
|
||||
Last commit: 5435209
|
||||
Last commit: af48e84
|
||||
Branch: main
|
||||
Feature branches merged: none
|
||||
|
||||
115
.paul/phases/61-payment-button-activation/61-01-PLAN.md
Normal file
115
.paul/phases/61-payment-button-activation/61-01-PLAN.md
Normal file
@@ -0,0 +1,115 @@
|
||||
---
|
||||
phase: 61-payment-button-activation
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- resources/views/orders/show.php
|
||||
- resources/scss/app.scss
|
||||
- public/assets/css/app.css
|
||||
autonomous: true
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Aktywacja przycisku "Platnosc" w headerze szczegółów zamówienia — kliknięcie przełącza na zakładkę płatności i automatycznie otwiera formularz dodawania płatności. Dodatkowo poprawa odstępu między inputami a buttonami w formularzu płatności.
|
||||
|
||||
## Purpose
|
||||
Przycisk "Płatność" jest obecnie nieaktywny (btn--disabled) mimo że zakładka płatności i formularz już istnieją (Phase 56). Aktywacja przycisku skraca ścieżkę użytkownika do dodania płatności.
|
||||
|
||||
## Output
|
||||
- Przycisk "Platnosc" aktywny, przełącza na tab `payments` i otwiera formularz
|
||||
- Formularz z wizualnym odstępem między inputami a przyciskami
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Source Files
|
||||
@resources/views/orders/show.php (linia 76: przycisk Platnosc, linia 128: tab payments, linia 559-638: panel + formularz)
|
||||
@resources/scss/app.scss (linia 1648-1687: style payment-add-form)
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Przycisk Platnosc aktywny i funkcjonalny
|
||||
```gherkin
|
||||
Given użytkownik jest na stronie szczegółów zamówienia /orders/{id}
|
||||
When klika przycisk "Platnosc" w headerze
|
||||
Then widok przełącza się na zakładkę "Płatności"
|
||||
And formularz dodawania płatności jest automatycznie widoczny (rozwinięty)
|
||||
```
|
||||
|
||||
## AC-2: Odstęp między inputami a buttonami formularza
|
||||
```gherkin
|
||||
Given formularz dodawania płatności jest widoczny
|
||||
When użytkownik widzi formularz
|
||||
Then między rzędem inputów a rzędem przycisków jest wyraźny odstęp (margin-top ≥ 12px)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Aktywacja przycisku Platnosc i podpięcie JS</name>
|
||||
<files>resources/views/orders/show.php</files>
|
||||
<action>
|
||||
1. Linia 76: zamień `<button type="button" class="btn btn--secondary btn--disabled">Platnosc</button>` na `<button type="button" class="btn btn--secondary" id="btn-header-payment">Platnosc</button>` — usunięcie btn--disabled i dodanie ID.
|
||||
2. W bloku `<script>` na dole pliku dodaj handler:
|
||||
- Nasłuchuje click na `#btn-header-payment`
|
||||
- Wywołuje `setActiveTab('payments')` aby przełączyć na zakładkę płatności
|
||||
- Ustawia `localStorage` (storageKey) na `'payments'`
|
||||
- Pokazuje formularz: `document.getElementById('payment-add-form').style.display = ''`
|
||||
</action>
|
||||
<verify>Otworzyć /orders/130, kliknąć przycisk "Platnosc" — widok przeskakuje na tab Płatności, formularz jest widoczny</verify>
|
||||
<done>AC-1 satisfied: przycisk aktywny, przełącza tab i otwiera formularz</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Odstęp w formularzu płatności (SCSS)</name>
|
||||
<files>resources/scss/app.scss, public/assets/css/app.css</files>
|
||||
<action>
|
||||
1. W SCSS (app.scss) do selektora `.payment-add-form__actions` dodaj `margin-top: 12px;` (aktualnie ma tylko gap: 8px bez marginesu górnego, klasa `mt-8` w HTML daje 8px co jest za mało).
|
||||
2. W HTML (show.php linia 634) zamień `mt-8` na `mt-12` lub zostaw HTML bez zmian i użyj samego SCSS.
|
||||
3. Zbuduj CSS: `npx sass resources/scss/app.scss public/assets/css/app.css --no-source-map`
|
||||
</action>
|
||||
<verify>Otworzyć formularz płatności — wyraźny odstęp między inputami a przyciskami Zapisz/Anuluj</verify>
|
||||
<done>AC-2 satisfied: wizualny odstęp ≥ 12px między inputami a buttonami</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- Logika backend płatności (PaymentController, PaymentRepository)
|
||||
- Pozostałe przyciski w headerze (Strefa klienta, Drukuj, Pakuj, Edytuj — zostają disabled)
|
||||
- Tab switching logic (setActiveTab) — reuse istniejącej funkcji
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Tylko aktywacja przycisku i poprawa CSS
|
||||
- Brak zmian w logice formularza zapisywania płatności
|
||||
- Brak zmian w API/endpointach
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Przycisk "Platnosc" nie ma klasy btn--disabled
|
||||
- [ ] Kliknięcie przełącza na tab payments
|
||||
- [ ] Formularz dodawania płatności jest automatycznie widoczny po kliknięciu
|
||||
- [ ] Odstęp między inputami a buttonami w formularzu ≥ 12px
|
||||
- [ ] Build SCSS przechodzi bez błędów
|
||||
- [ ] Pozostałe przyciski disabled bez zmian
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Przycisk Platnosc aktywny i funkcjonalny
|
||||
- Formularz otwarty automatycznie po kliknięciu
|
||||
- Wizualny odstęp w formularzu poprawiony
|
||||
- Brak regresji w pozostałych zakładkach i przyciskach
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/61-payment-button-activation/61-01-SUMMARY.md`
|
||||
</output>
|
||||
96
.paul/phases/61-payment-button-activation/61-01-SUMMARY.md
Normal file
96
.paul/phases/61-payment-button-activation/61-01-SUMMARY.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
phase: 61-payment-button-activation
|
||||
plan: 01
|
||||
subsystem: ui
|
||||
tags: [button, payments, scss, javascript]
|
||||
|
||||
requires:
|
||||
- phase: 56-order-payments
|
||||
provides: zakładka płatności z formularzem dodawania
|
||||
provides:
|
||||
- aktywny przycisk Platnosc w headerze zamówienia
|
||||
- automatyczne otwarcie formularza płatności po kliknięciu
|
||||
- poprawiony odstęp w formularzu płatności
|
||||
affects: []
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: []
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- resources/views/orders/show.php
|
||||
- resources/scss/app.scss
|
||||
- public/assets/css/app.css
|
||||
|
||||
key-decisions:
|
||||
- "Reuse istniejącej funkcji setActiveTab() zamiast duplikacji logiki"
|
||||
|
||||
patterns-established: []
|
||||
|
||||
duration: 3min
|
||||
started: 2026-03-31
|
||||
completed: 2026-03-31
|
||||
---
|
||||
|
||||
# Phase 61 Plan 01: Payment Button Activation Summary
|
||||
|
||||
**Aktywacja przycisku "Platnosc" w headerze zamówienia — kliknięcie przełącza na tab płatności i otwiera formularz dodawania + poprawa odstępu między inputami a buttonami.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~3min |
|
||||
| Started | 2026-03-31 |
|
||||
| Completed | 2026-03-31 |
|
||||
| Tasks | 2 completed |
|
||||
| Files modified | 3 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Przycisk Platnosc aktywny i funkcjonalny | Pass | Usunięto btn--disabled, dodano ID + handler JS przełączający tab i otwierający formularz |
|
||||
| AC-2: Odstęp między inputami a buttonami formularza | Pass | margin-top: 12px w .payment-add-form__actions |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Przycisk "Platnosc" aktywny — kliknięcie przełącza na zakładkę płatności i automatycznie otwiera formularz dodawania
|
||||
- Poprawa wizualnego odstępu w formularzu płatności (margin-top: 12px)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `resources/views/orders/show.php` | Modified | Usunięto btn--disabled z przycisku Platnosc, dodano id="btn-header-payment", handler JS |
|
||||
| `resources/scss/app.scss` | Modified | margin-top: 12px w .payment-add-form__actions |
|
||||
| `public/assets/css/app.css` | Modified | Zbudowany CSS z nowym marginesem |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
None - followed plan as specified
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Wszystkie przyciski akcji w headerze zamówienia mają spójny wzorzec aktywacji
|
||||
|
||||
**Concerns:**
|
||||
- Pozostałe przyciski (Strefa klienta, Drukuj, Pakuj, Edytuj) nadal disabled — do aktywacji w przyszłych fazach
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 61-payment-button-activation, Plan: 01*
|
||||
*Completed: 2026-03-31*
|
||||
176
.paul/phases/62-import-reimport-safety/62-01-PLAN.md
Normal file
176
.paul/phases/62-import-reimport-safety/62-01-PLAN.md
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
phase: 62-import-reimport-safety
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/Modules/Settings/ShopproOrdersSyncService.php
|
||||
- src/Modules/Orders/OrderImportRepository.php
|
||||
autonomous: true
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Naprawa dwóch problemów z re-importem zamówień:
|
||||
1. Rozróżnienie "Import" vs "Aktualizacja" w activity log dla shopPRO (analogicznie do Allegro) + deduplikacja
|
||||
2. Ochrona danych lokalnych (płatności, historia statusów, przesyłki) przed nadpisaniem przy re-imporcie — dla obu źródeł (shopPRO i Allegro)
|
||||
|
||||
## Purpose
|
||||
Obecnie re-import zamówienia kasuje DELETE+INSERT dane, które mogły zostać dodane lokalnie w orderPRO (ręczna płatność, zmiana statusu, przesyłka). Dodatkowo shopPRO loguje identyczne wpisy "Import zamówienia" przy każdym re-imporcie zamiast rozróżniać import od aktualizacji.
|
||||
|
||||
## Output
|
||||
- shopPRO activity log: "Import zamowienia z shopPRO" vs "Zaktualizowano zamowienie z shopPRO (re-import)" + deduplikacja jak Allegro
|
||||
- `upsertOrderAggregate` przy UPDATE pomija replace płatności, historii statusów i przesyłek (chroni dane lokalne)
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Source Files
|
||||
@src/Modules/Settings/ShopproOrdersSyncService.php (linia 205-259: importOneOrder — brak rozróżnienia create/update w activity log)
|
||||
@src/Modules/Settings/AllegroOrderImportService.php (linia 58-103: wzorcowa implementacja — rozróżnia import/aktualizacja + shouldSkipDuplicate)
|
||||
@src/Modules/Orders/OrderImportRepository.php (linia 25-64: upsertOrderAggregate — zawsze DELETE+INSERT na wszystkich relacjach)
|
||||
@src/Modules/Orders/OrdersRepository.php (linia 765-813: shouldSkipDuplicateImportActivity — istniejąca logika deduplikacji)
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: shopPRO activity log rozróżnia import od aktualizacji
|
||||
```gherkin
|
||||
Given zamówienie z shopPRO już istnieje w orderPRO
|
||||
When cron re-importuje to zamówienie (source_updated_at się zmieniło)
|
||||
Then w activity log pojawia się "Zaktualizowano zamowienie z shopPRO (re-import)" zamiast "Import zamowienia z shopPRO"
|
||||
```
|
||||
|
||||
## AC-2: shopPRO activity log deduplikuje identyczne wpisy
|
||||
```gherkin
|
||||
Given zamówienie z shopPRO zostało właśnie zaimportowane/zaktualizowane
|
||||
When cron próbuje re-importować z tym samym source_updated_at
|
||||
Then nowy wpis w activity log NIE jest tworzony (shouldSkipDuplicateImportActivity)
|
||||
```
|
||||
|
||||
## AC-3: Re-import nie kasuje lokalnych płatności
|
||||
```gherkin
|
||||
Given zamówienie ma płatność dodaną ręcznie w orderPRO
|
||||
When cron re-importuje to zamówienie z shopPRO lub Allegro
|
||||
Then ręcznie dodana płatność nie jest usunięta
|
||||
And płatności z importu są nadal poprawnie zapisywane przy pierwszym imporcie
|
||||
```
|
||||
|
||||
## AC-4: Re-import nie kasuje lokalnej historii statusów
|
||||
```gherkin
|
||||
Given zamówienie ma zmianę statusu wykonaną w orderPRO
|
||||
When cron re-importuje to zamówienie
|
||||
Then lokalna historia statusów nie jest usunięta
|
||||
And historia statusów z importu jest poprawnie zapisywana przy pierwszym imporcie
|
||||
```
|
||||
|
||||
## AC-5: Re-import nie kasuje lokalnych przesyłek importowych
|
||||
```gherkin
|
||||
Given zamówienie ma przesyłki zaimportowane lub utworzone w orderPRO
|
||||
When cron re-importuje to zamówienie
|
||||
Then przesyłki z order_shipments nie są usunięte
|
||||
And przesyłki z importu są poprawnie zapisywane przy pierwszym imporcie
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Activity log shopPRO — rozróżnienie import/aktualizacja + deduplikacja</name>
|
||||
<files>src/Modules/Settings/ShopproOrdersSyncService.php</files>
|
||||
<action>
|
||||
W metodzie `importOneOrder()` (linia 239-246) zmienić blok activity log analogicznie do AllegroOrderImportService (linia 71-94):
|
||||
|
||||
1. Po linii 234 (po sprawdzeniu `$save['created']`) dodać logikę:
|
||||
- Ustalić `$wasCreated = !empty($save['created'])`
|
||||
- Ustalić `$summary`:
|
||||
- Jeśli `$wasCreated`: `'Import zamowienia z shopPRO'`
|
||||
- Jeśli nie: `'Zaktualizowano zamowienie z shopPRO (re-import)'`
|
||||
2. Rozszerzyć `$details` o pola potrzebne do deduplikacji:
|
||||
- `'source_updated_at' => $sourceUpdatedAt`
|
||||
- `'created' => $wasCreated`
|
||||
- `'trigger' => 'orders_sync'`
|
||||
- `'trigger_label' => 'Synchronizacja zamowien'`
|
||||
3. Owinąć `recordActivity()` w warunek `shouldSkipDuplicateImportActivity()`:
|
||||
```php
|
||||
if (!$this->orders->shouldSkipDuplicateImportActivity((int) ($save['order_id'] ?? 0), $details)) {
|
||||
$this->orders->recordActivity(...);
|
||||
}
|
||||
```
|
||||
|
||||
Wzorzec: AllegroOrderImportService linia 71-94.
|
||||
</action>
|
||||
<verify>Przegląd kodu: shopPRO importOneOrder używa shouldSkipDuplicateImportActivity i rozróżnia summary</verify>
|
||||
<done>AC-1 i AC-2 satisfied</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: upsertOrderAggregate — pominięcie replace płatności/historii/przesyłek przy UPDATE</name>
|
||||
<files>src/Modules/Orders/OrderImportRepository.php</files>
|
||||
<action>
|
||||
W metodzie `upsertOrderAggregate()` (linia 25-64):
|
||||
|
||||
1. Przenieść wywołania `replacePayments`, `replaceShipments` i `replaceStatusHistory` do bloku warunkowego `if ($created)`:
|
||||
```php
|
||||
$this->replaceAddresses($orderId, $addresses);
|
||||
$this->replaceItems($orderId, $items);
|
||||
$this->replaceNotes($orderId, $notes);
|
||||
|
||||
if ($created) {
|
||||
$this->replacePayments($orderId, $payments);
|
||||
$this->replaceShipments($orderId, $shipments);
|
||||
$this->replaceStatusHistory($orderId, $statusHistory);
|
||||
}
|
||||
```
|
||||
|
||||
2. Logika:
|
||||
- Adresy i pozycje (items) — nadpisywane ZAWSZE (dane pochodzą ze źródła, nie są edytowane lokalnie)
|
||||
- Notatki — nadpisywane ZAWSZE (pochodzą ze źródła)
|
||||
- Płatności — tylko przy INSERT (ręczne płatności dodane w orderPRO nie zostaną skasowane)
|
||||
- Przesyłki (order_shipments) — tylko przy INSERT (lokalne przesyłki nie zostaną skasowane)
|
||||
- Historia statusów — tylko przy INSERT (lokalne zmiany statusów nie zostaną skasowane)
|
||||
|
||||
Ta zmiana dotyczy OBIE źródeł (shopPRO i Allegro) bo obie korzystają z tego samego repozytorium.
|
||||
</action>
|
||||
<verify>Przegląd kodu: replacePayments, replaceShipments, replaceStatusHistory wywoływane tylko gdy $created === true</verify>
|
||||
<done>AC-3, AC-4 i AC-5 satisfied</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- AllegroOrderImportService.php — Allegro już ma poprawną logikę activity log, nie modyfikować
|
||||
- OrdersRepository.php — shouldSkipDuplicateImportActivity() działa poprawnie, reuse bez zmian
|
||||
- Logika kursora (ShopproOrderMapper, ShopproOrderSyncStateRepository) — nie modyfikować
|
||||
- Tabele DB — brak migracji
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Brak kolumny `origin` w tabelach (to byłby etap 2, poza zakresem)
|
||||
- Nie zmieniamy logiki replace dla adresów, pozycji ani notatek (te dane pochodzą wyłącznie ze źródła)
|
||||
- Nie dodajemy nowego merge/diff — po prostu pomijamy replace przy update
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] shopPRO importOneOrder rozróżnia "Import" vs "Zaktualizowano (re-import)" w summary
|
||||
- [ ] shopPRO importOneOrder używa shouldSkipDuplicateImportActivity
|
||||
- [ ] upsertOrderAggregate: replacePayments/replaceShipments/replaceStatusHistory wywołane tylko przy $created
|
||||
- [ ] upsertOrderAggregate: replaceAddresses/replaceItems/replaceNotes wywołane zawsze (bez zmian)
|
||||
- [ ] Brak zmian w AllegroOrderImportService
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Activity log shopPRO rozróżnia import od aktualizacji
|
||||
- Duplikaty activity log eliminowane przez shouldSkipDuplicateImportActivity
|
||||
- Re-import nie kasuje lokalnych płatności, przesyłek ani historii statusów
|
||||
- Pierwsze importowanie zapisuje wszystkie dane normalnie
|
||||
- Brak regresji w imporcie Allegro
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/62-import-reimport-safety/62-01-SUMMARY.md`
|
||||
</output>
|
||||
106
.paul/phases/62-import-reimport-safety/62-01-SUMMARY.md
Normal file
106
.paul/phases/62-import-reimport-safety/62-01-SUMMARY.md
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
phase: 62-import-reimport-safety
|
||||
plan: 01
|
||||
subsystem: api
|
||||
tags: [import, activity-log, deduplication, data-protection]
|
||||
|
||||
requires:
|
||||
- phase: 56-order-payments
|
||||
provides: ręczne płatności w orderPRO które mogą być nadpisane
|
||||
provides:
|
||||
- rozróżnienie import vs aktualizacja w activity log shopPRO
|
||||
- deduplikacja wpisów activity log shopPRO (shouldSkipDuplicateImportActivity)
|
||||
- ochrona lokalnych płatności, przesyłek i historii statusów przed nadpisaniem przy re-imporcie
|
||||
affects: []
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "replacePayments/Shipments/StatusHistory tylko przy INSERT (nie UPDATE) w upsertOrderAggregate"
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- src/Modules/Settings/ShopproOrdersSyncService.php
|
||||
- src/Modules/Orders/OrderImportRepository.php
|
||||
|
||||
key-decisions:
|
||||
- "Adresy, pozycje i notatki nadpisywane zawsze (dane ze źródła); płatności, przesyłki i historia statusów tylko przy pierwszym imporcie"
|
||||
- "Reuse shouldSkipDuplicateImportActivity z Allegro zamiast nowej logiki"
|
||||
|
||||
patterns-established:
|
||||
- "Przy re-imporcie chronić dane które mogą pochodzić z lokalnych akcji użytkownika"
|
||||
|
||||
duration: 3min
|
||||
started: 2026-03-31
|
||||
completed: 2026-03-31
|
||||
---
|
||||
|
||||
# Phase 62 Plan 01: Import Re-import Safety Summary
|
||||
|
||||
**Rozróżnienie import/aktualizacja w activity log shopPRO + ochrona lokalnych danych (płatności, przesyłki, historia statusów) przed nadpisaniem przy re-imporcie dla obu źródeł.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~3min |
|
||||
| Started | 2026-03-31 |
|
||||
| Completed | 2026-03-31 |
|
||||
| Tasks | 2 completed |
|
||||
| Files modified | 2 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: shopPRO rozróżnia import od aktualizacji | Pass | "Import zamowienia z shopPRO" vs "Zaktualizowano zamowienie z shopPRO (re-import)" |
|
||||
| AC-2: shopPRO deduplikuje identyczne wpisy | Pass | shouldSkipDuplicateImportActivity reuse z Allegro |
|
||||
| AC-3: Re-import nie kasuje lokalnych płatności | Pass | replacePayments tylko przy $created |
|
||||
| AC-4: Re-import nie kasuje lokalnej historii statusów | Pass | replaceStatusHistory tylko przy $created |
|
||||
| AC-5: Re-import nie kasuje lokalnych przesyłek | Pass | replaceShipments tylko przy $created |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- shopPRO activity log rozróżnia pierwszy import od re-importu (analogicznie do Allegro)
|
||||
- Deduplikacja wpisów activity log eliminuje powtórzenia przy tym samym source_updated_at
|
||||
- Re-import (UPDATE) nie kasuje już płatności, przesyłek ani historii statusów — chroni dane dodane lokalnie w orderPRO
|
||||
- Zmiana w OrderImportRepository dotyczy obu źródeł (shopPRO i Allegro)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `src/Modules/Settings/ShopproOrdersSyncService.php` | Modified | Rozróżnienie summary import/aktualizacja, dodanie pól deduplikacji, warunek shouldSkipDuplicate |
|
||||
| `src/Modules/Orders/OrderImportRepository.php` | Modified | replacePayments/Shipments/StatusHistory przeniesione do bloku if ($created) |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Adresy/pozycje/notatki nadpisywane zawsze | Dane pochodzą wyłącznie ze źródła, nie są edytowane lokalnie | Źródło pozostaje autorytatywne dla tych danych |
|
||||
| Płatności/przesyłki/historia tylko przy INSERT | Mogą być dodawane lokalnie w orderPRO (ręczna płatność, zmiana statusu) | Lokalne dane chronione przed kaskadowym DELETE |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Import bezpieczny dla danych lokalnych
|
||||
- Activity log spójny między Allegro i shopPRO
|
||||
|
||||
**Concerns:**
|
||||
- Brak kolumny `origin` (source/local) na tabelach relacyjnych — przy przyszłej potrzebie merge/diff danych z obu źródeł trzeba będzie dodać
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 62-import-reimport-safety, Plan: 01*
|
||||
*Completed: 2026-03-31*
|
||||
116
.vscode/ftp-kr.sync.cache.json
vendored
116
.vscode/ftp-kr.sync.cache.json
vendored
@@ -553,7 +553,13 @@
|
||||
"20260330_000073_create_order_payments_table.sql": {
|
||||
"type": "-",
|
||||
"size": 2507,
|
||||
"lmtime": 1774904625009,
|
||||
"lmtime": 1774904625000,
|
||||
"modified": false
|
||||
},
|
||||
"20260331_000074_seed_order_status_aged_cron.sql": {
|
||||
"type": "-",
|
||||
"size": 481,
|
||||
"lmtime": 1774909414360,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
@@ -576,14 +582,14 @@
|
||||
"DOCS": {
|
||||
"ARCHITECTURE.md": {
|
||||
"type": "-",
|
||||
"size": 40166,
|
||||
"lmtime": 1774905621286,
|
||||
"size": 40484,
|
||||
"lmtime": 1774909776700,
|
||||
"modified": false
|
||||
},
|
||||
"DB_SCHEMA.md": {
|
||||
"type": "-",
|
||||
"size": 33256,
|
||||
"lmtime": 1774904646453,
|
||||
"lmtime": 1774904646000,
|
||||
"modified": false
|
||||
},
|
||||
"ORDERS_SCHEMA_APILO_DRAFT.md": {
|
||||
@@ -606,8 +612,8 @@
|
||||
},
|
||||
"TECH_CHANGELOG.md": {
|
||||
"type": "-",
|
||||
"size": 69323,
|
||||
"lmtime": 1774906997086,
|
||||
"size": 71649,
|
||||
"lmtime": 1774909762244,
|
||||
"modified": false
|
||||
},
|
||||
"todo.md": {
|
||||
@@ -1830,8 +1836,8 @@
|
||||
"css": {
|
||||
"app.css": {
|
||||
"type": "-",
|
||||
"size": 57932,
|
||||
"lmtime": 1774874372180,
|
||||
"size": 57250,
|
||||
"lmtime": 1774820920578,
|
||||
"modified": false
|
||||
},
|
||||
"app.css.map": {
|
||||
@@ -1866,8 +1872,8 @@
|
||||
"modules": {
|
||||
"automation-form.js": {
|
||||
"type": "-",
|
||||
"size": 9406,
|
||||
"lmtime": 1774905596185,
|
||||
"size": 10533,
|
||||
"lmtime": 1774909546633,
|
||||
"modified": false
|
||||
},
|
||||
"inline-status-change.js": {
|
||||
@@ -1927,8 +1933,8 @@
|
||||
"scss": {
|
||||
"app.css": {
|
||||
"type": "-",
|
||||
"size": 57932,
|
||||
"lmtime": 1774874360396,
|
||||
"size": 41813,
|
||||
"lmtime": 1773532822690,
|
||||
"modified": false
|
||||
},
|
||||
"app.css.map": {
|
||||
@@ -1939,8 +1945,8 @@
|
||||
},
|
||||
"app.scss": {
|
||||
"type": "-",
|
||||
"size": 46381,
|
||||
"lmtime": 1774874345832,
|
||||
"size": 45727,
|
||||
"lmtime": 1774820913052,
|
||||
"modified": false
|
||||
},
|
||||
"login.css": {
|
||||
@@ -2097,8 +2103,8 @@
|
||||
},
|
||||
"show.php": {
|
||||
"type": "-",
|
||||
"size": 50549,
|
||||
"lmtime": 1774874320939,
|
||||
"size": 41726,
|
||||
"lmtime": 1774820344044,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
@@ -2258,14 +2264,14 @@
|
||||
"automation": {
|
||||
"form.php": {
|
||||
"type": "-",
|
||||
"size": 15894,
|
||||
"lmtime": 1774906988194,
|
||||
"size": 17530,
|
||||
"lmtime": 1774909526182,
|
||||
"modified": false
|
||||
},
|
||||
"index.php": {
|
||||
"type": "-",
|
||||
"size": 15170,
|
||||
"lmtime": 1774703149752,
|
||||
"size": 15349,
|
||||
"lmtime": 1774909530877,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
@@ -2275,10 +2281,22 @@
|
||||
"web.php": {
|
||||
"type": "-",
|
||||
"size": 27598,
|
||||
"lmtime": 1774905481955,
|
||||
"lmtime": 1774905481000,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
".serena": {
|
||||
"cache": {
|
||||
"php": {
|
||||
"raw_document_symbols.pkl": {
|
||||
"type": "-",
|
||||
"size": 4100962,
|
||||
"lmtime": 1774906250000,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"src": {
|
||||
"Core": {
|
||||
"Application.php": {
|
||||
@@ -2494,8 +2512,8 @@
|
||||
},
|
||||
"CronHandlerFactory.php": {
|
||||
"type": "-",
|
||||
"size": 8757,
|
||||
"lmtime": 1774905521818,
|
||||
"size": 9103,
|
||||
"lmtime": 1774909459947,
|
||||
"modified": false
|
||||
},
|
||||
"CronJobProcessor.php": {
|
||||
@@ -2528,6 +2546,12 @@
|
||||
"lmtime": 1772655073134,
|
||||
"modified": false
|
||||
},
|
||||
"OrderStatusAgedHandler.php": {
|
||||
"type": "-",
|
||||
"size": 550,
|
||||
"lmtime": 1774909441386,
|
||||
"modified": false
|
||||
},
|
||||
"ProductLinksHealthCheckHandler.php": {
|
||||
"type": "-",
|
||||
"size": 5247,
|
||||
@@ -2640,15 +2664,15 @@
|
||||
},
|
||||
"OrdersController.php": {
|
||||
"type": "-",
|
||||
"size": 39402,
|
||||
"lmtime": 1774905472960,
|
||||
"size": 40092,
|
||||
"lmtime": 1774907857219,
|
||||
"modified": false
|
||||
},
|
||||
"OrdersRepository.php": {
|
||||
"type": "-",
|
||||
"size": 37722,
|
||||
"lmtime": 1774874236106,
|
||||
"modified": false
|
||||
"lmtime": 1774474648512,
|
||||
"modified": true
|
||||
},
|
||||
"OrderStatusSyncService.php": {
|
||||
"type": "-",
|
||||
@@ -2971,8 +2995,8 @@
|
||||
"ShopproApiClient.php": {
|
||||
"type": "-",
|
||||
"size": 13506,
|
||||
"lmtime": 1774874195967,
|
||||
"modified": false
|
||||
"lmtime": 1774612664232,
|
||||
"modified": true
|
||||
},
|
||||
"ShopProClient.php": {
|
||||
"type": "-",
|
||||
@@ -3019,7 +3043,7 @@
|
||||
"ShopproPaymentStatusSyncService.php": {
|
||||
"type": "-",
|
||||
"size": 14390,
|
||||
"lmtime": 1774905506418,
|
||||
"lmtime": 1774905506000,
|
||||
"modified": false
|
||||
},
|
||||
"ShopproProductImageResolver.php": {
|
||||
@@ -3156,8 +3180,8 @@
|
||||
"Automation": {
|
||||
"AutomationController.php": {
|
||||
"type": "-",
|
||||
"size": 22513,
|
||||
"lmtime": 1774906921315,
|
||||
"size": 23891,
|
||||
"lmtime": 1774909489455,
|
||||
"modified": false
|
||||
},
|
||||
"AutomationExecutionLogRepository.php": {
|
||||
@@ -3174,8 +3198,14 @@
|
||||
},
|
||||
"AutomationService.php": {
|
||||
"type": "-",
|
||||
"size": 30415,
|
||||
"lmtime": 1774905433588,
|
||||
"size": 32678,
|
||||
"lmtime": 1774909501988,
|
||||
"modified": false
|
||||
},
|
||||
"OrderStatusAgedService.php": {
|
||||
"type": "-",
|
||||
"size": 5040,
|
||||
"lmtime": 1774909434600,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
@@ -5357,24 +5387,6 @@
|
||||
"phpmailer": {
|
||||
"phpmailer": {}
|
||||
}
|
||||
},
|
||||
".serena": {
|
||||
"cache": {
|
||||
"php": {
|
||||
"document_symbols.pkl": {
|
||||
"type": "-",
|
||||
"size": 13361425,
|
||||
"lmtime": 1774906273656,
|
||||
"modified": false
|
||||
},
|
||||
"raw_document_symbols.pkl": {
|
||||
"type": "-",
|
||||
"size": 4107469,
|
||||
"lmtime": 1774906272460,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2393,6 +2393,7 @@ details[open] > .order-statuses-side__title .order-statuses-side__arrow {
|
||||
.payment-add-form__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.order-kv dt {
|
||||
|
||||
@@ -1684,6 +1684,7 @@ details[open] > .order-statuses-side__title .order-statuses-side__arrow {
|
||||
.payment-add-form__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.order-kv dt {
|
||||
|
||||
@@ -57,7 +57,7 @@ foreach ($addressesList as $address) {
|
||||
</div>
|
||||
<div class="order-details-actions">
|
||||
<button type="button" class="btn btn--secondary btn--disabled">Strefa klienta</button>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/prepare" class="btn btn--secondary">Przygotuj przesylke</a>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/prepare" class="btn btn--primary">Przygotuj przesylke</a>
|
||||
<?php if ($receiptConfigsList !== []): ?>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/receipt/create" class="btn btn--secondary">Wystaw paragon</a>
|
||||
<?php endif; ?>
|
||||
@@ -73,7 +73,7 @@ foreach ($addressesList as $address) {
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn--secondary btn--disabled" title="Skonfiguruj skrzynke i szablony w Ustawieniach">Wyslij e-mail</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn--secondary btn--disabled">Platnosc</button>
|
||||
<button type="button" class="btn btn--secondary" id="btn-header-payment">Platnosc</button>
|
||||
<button type="button" class="btn btn--secondary btn--disabled">Drukuj</button>
|
||||
<button type="button" class="btn btn--primary btn--disabled">Pakuj</button>
|
||||
<button type="button" class="btn btn--secondary btn--disabled">Edytuj</button>
|
||||
@@ -781,6 +781,17 @@ foreach ($addressesList as $address) {
|
||||
try { savedTab = localStorage.getItem(storageKey); } catch (e) {}
|
||||
setActiveTab(forceTab || savedTab || 'details');
|
||||
|
||||
// Header "Platnosc" button — switch to payments tab and open form
|
||||
var btnHeaderPayment = document.getElementById('btn-header-payment');
|
||||
if (btnHeaderPayment) {
|
||||
btnHeaderPayment.addEventListener('click', function () {
|
||||
setActiveTab('payments');
|
||||
try { localStorage.setItem(storageKey, 'payments'); } catch (e) {}
|
||||
var form = document.getElementById('payment-add-form');
|
||||
if (form) form.style.display = '';
|
||||
});
|
||||
}
|
||||
|
||||
// Print label button handler (delegated for dynamically added buttons)
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('.btn-print-label');
|
||||
|
||||
@@ -44,10 +44,13 @@ final class OrderImportRepository
|
||||
|
||||
$this->replaceAddresses($orderId, $addresses);
|
||||
$this->replaceItems($orderId, $items);
|
||||
$this->replacePayments($orderId, $payments);
|
||||
$this->replaceShipments($orderId, $shipments);
|
||||
$this->replaceNotes($orderId, $notes);
|
||||
$this->replaceStatusHistory($orderId, $statusHistory);
|
||||
|
||||
if ($created) {
|
||||
$this->replacePayments($orderId, $payments);
|
||||
$this->replaceShipments($orderId, $shipments);
|
||||
$this->replaceStatusHistory($orderId, $statusHistory);
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
} catch (Throwable $exception) {
|
||||
|
||||
@@ -236,14 +236,30 @@ final class ShopproOrdersSyncService
|
||||
} else {
|
||||
$result['imported_updated'] = (int) $result['imported_updated'] + 1;
|
||||
}
|
||||
$this->orders->recordActivity(
|
||||
(int) ($save['order_id'] ?? 0),
|
||||
'import',
|
||||
'Import zamowienia z shopPRO',
|
||||
['integration_id' => $integrationId, 'source_order_id' => $sourceOrderId],
|
||||
'import',
|
||||
'shopPRO'
|
||||
);
|
||||
$wasCreated = !empty($save['created']);
|
||||
$savedOrderId = (int) ($save['order_id'] ?? 0);
|
||||
$summary = $wasCreated
|
||||
? 'Import zamowienia z shopPRO'
|
||||
: 'Zaktualizowano zamowienie z shopPRO (re-import)';
|
||||
$details = [
|
||||
'integration_id' => $integrationId,
|
||||
'source_order_id' => $sourceOrderId,
|
||||
'source_updated_at' => $sourceUpdatedAt,
|
||||
'created' => $wasCreated,
|
||||
'trigger' => 'orders_sync',
|
||||
'trigger_label' => 'Synchronizacja zamowien',
|
||||
];
|
||||
|
||||
if (!$this->orders->shouldSkipDuplicateImportActivity($savedOrderId, $details)) {
|
||||
$this->orders->recordActivity(
|
||||
$savedOrderId,
|
||||
'import',
|
||||
$summary,
|
||||
$details,
|
||||
'import',
|
||||
'shopPRO'
|
||||
);
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
$result['failed'] = (int) $result['failed'] + 1;
|
||||
$errors = is_array($result['errors']) ? $result['errors'] : [];
|
||||
|
||||
Reference in New Issue
Block a user