From 6d0905d97ad95040b638182d5fa364fa84831a49 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Tue, 7 Apr 2026 08:21:38 +0200 Subject: [PATCH] feat(attributes-import): import product attributes from shopPRO API into personalization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extractPersonalization() now reads both 'attributes' and 'custom_fields' fields from the shopPRO API response, joining non-empty values with newline separator. Previously only custom_fields was imported, causing product attributes like "Woreczek jutowy", "Kolor koperty", "Zakrętka" to be lost during sync. Co-Authored-By: Claude Opus 4.6 (1M context) --- .paul/PROJECT.md | 1 + .paul/ROADMAP.md | 1 + .paul/STATE.md | 22 ++-- .../phases/71-attributes-import/71-01-PLAN.md | 120 ++++++++++++++++++ .../71-attributes-import/71-01-SUMMARY.md | 101 +++++++++++++++ DOCS/TECH_CHANGELOG.md | 7 + src/Modules/Settings/ShopproOrderMapper.php | 21 +-- 7 files changed, 254 insertions(+), 19 deletions(-) create mode 100644 .paul/phases/71-attributes-import/71-01-PLAN.md create mode 100644 .paul/phases/71-attributes-import/71-01-SUMMARY.md diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index fd58b94..39c3126 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -74,6 +74,7 @@ Sprzedawca moĹĽe obsĹ‚ugiwać zamĂłwienia ze wszystkich kanałów - [x] Import i wyswietlanie personalizacji produktow z shopPRO (custom_fields) + naprawa daty zamowienia — Phase 63 - [x] Data wystawienia paragonu z dokladnoscia do godziny i minuty (DATE -> DATETIME) — Phase 64 - [x] Koszt wysylki jako pozycja paragonu (bugfix buildItemsSnapshot + delivery_price) — Phase 70 +- [x] Import atrybutow produktow z shopPRO (attributes + custom_fields w personalizacji) — Phase 71 - [ ] Eliminacja zduplikowanego kodu: SslCertificateResolver, ToggleableRepositoryTrait, RedirectPathResolver, ReceiptService — Phase 68 ### Active (In Progress) diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 229b1aa..3cc3412 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -31,6 +31,7 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel | 68 | Code Deduplication Refactor | 0/2 | Planning | | 69 | Allegro Tracking English Statuses | 1/1 | Complete | | 70 | Receipt Shipping Cost | 1/1 | Complete | +| 71 | Attributes Import | 1/1 | Complete | | TBD | Mobile Orders List | - | Not started | | TBD | Mobile Order Details | - | Not started | | TBD | Mobile Settings | - | Not started | diff --git a/.paul/STATE.md b/.paul/STATE.md index f3bc290..37ef811 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -2,22 +2,22 @@ ## Project Reference -See: .paul/PROJECT.md (updated 2026-04-04) +See: .paul/PROJECT.md (updated 2026-04-07) **Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami. -**Current focus:** Milestone v3.0 - Phase 70 complete, ready for next PLAN +**Current focus:** Milestone v3.0 - Phase 71 complete, ready for next PLAN ## Current Position Milestone: v3.0 Mobile Responsive - In progress -Phase: 70 (Receipt Shipping Cost) — Complete -Plan: 70-01 unified +Phase: 71 (Attributes Import) — Complete +Plan: 71-01 unified Status: Loop complete, ready for next PLAN -Last activity: 2026-04-06 — Unified .paul/phases/70-receipt-shipping-cost/70-01-PLAN.md +Last activity: 2026-04-07 — Unified .paul/phases/71-attributes-import/71-01-PLAN.md Progress: -- Milestone: [#######...] ~72% -- Phase 70: [##########] 100% +- Milestone: [#######...] ~74% +- Phase 71: [##########] 100% ## Loop Position @@ -29,12 +29,12 @@ PLAN --> APPLY --> UNIFY ## Session Continuity -Last session: 2026-04-06 -Stopped at: Plan 70-01 unified +Last session: 2026-04-07 +Stopped at: Plan 71-01 unified Next action: Run /paul:plan for the next prioritized phase -Resume file: .paul/phases/70-receipt-shipping-cost/70-01-SUMMARY.md +Resume file: .paul/phases/71-attributes-import/71-01-SUMMARY.md ## Git State -Last commit: 0e7ee95 +Last commit: 278f44b Branch: main diff --git a/.paul/phases/71-attributes-import/71-01-PLAN.md b/.paul/phases/71-attributes-import/71-01-PLAN.md new file mode 100644 index 0000000..a547b26 --- /dev/null +++ b/.paul/phases/71-attributes-import/71-01-PLAN.md @@ -0,0 +1,120 @@ +--- +phase: 71-attributes-import +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: [src/Modules/Settings/ShopproOrderMapper.php] +autonomous: true +--- + + +## Goal +Dodanie importu pola `attributes` z API shopPRO do personalizacji produktow w orderPRO. Obecnie importowane jest tylko `custom_fields`, a `attributes` (np. "Woreczek jutowy: Nie", "Kolor koperty: Biala") jest ignorowane. + +## Purpose +Klienci nie widza pelnych danych produktu z zamowienia shopPRO. Atrybuty takie jak wariant produktu, kolor, dodatkowe opcje sa tracone przy imporcie. + +## Output +Zmodyfikowany `ShopproOrderMapper::extractPersonalization()` ktory laczy `attributes` i `custom_fields` w jedno pole `personalization`. + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md + +## Source Files +@src/Modules/Settings/ShopproOrderMapper.php (linie 590-602 - extractPersonalization) + + + + +## AC-1: Atrybuty produktu importowane do personalizacji +```gherkin +Given zamowienie w shopPRO ma produkt z polem attributes = "Woreczek jutowy: Nie" +When zamowienie jest synchronizowane do orderPRO +Then pole personalization zawiera "Woreczek jutowy: Nie" +``` + +## AC-2: Polaczenie attributes i custom_fields +```gherkin +Given produkt ma attributes = "Kolor tekstu: Bialy
Zakretka: Zlota" i custom_fields = "Imiona: Jan i Anna" +When zamowienie jest synchronizowane +Then personalization zawiera oba bloki oddzielone nowa linia (najpierw attributes, potem custom_fields) +``` + +## AC-3: Puste pola nie generuja pustych linii +```gherkin +Given produkt ma attributes = "" i custom_fields = "Imie: Jan" +When zamowienie jest synchronizowane +Then personalization zawiera tylko "Imie: Jan" (bez pustych linii na poczatku) +``` + +## AC-4: Oba pola puste zwracaja null +```gherkin +Given produkt ma attributes = "" i custom_fields = "" +When zamowienie jest synchronizowane +Then personalization = null (bez zmian wzgledem obecnego zachowania) +``` + +
+ + + + + Task 1: Rozszerzenie extractPersonalization o pole attributes + src/Modules/Settings/ShopproOrderMapper.php + + W metodzie `extractPersonalization()` (linia 590): + 1. Odczytaj pole `attributes` z $row (readPath z kluczem 'attributes') + 2. Odczytaj pole `custom_fields` z $row (juz istniejace) + 3. Dla kazdego niepustego pola: zamien <br> na \n, strip_tags, html_entity_decode, trim + 4. Polacz niepuste czesci separatorem "\n" (attributes first, potem custom_fields) + 5. Zwroc polaczony tekst lub null jesli oba puste + + Nie zmieniac logiki parsowania HTML (str_replace br, strip_tags, html_entity_decode) - tylko rozszerzyc o drugie pole. + Nie dodawac naglowkow "Atrybuty:" / "Personalizacja:" - traktowac dane jednorodnie. + + + Sprawdzenie w bazie danych po resync zamowienia 11776: + SELECT personalization FROM order_items WHERE order_id = (SELECT id FROM orders WHERE source_order_id = '11776') + Powinno zwrocic "Woreczek jutowy: Nie" + + AC-1, AC-2, AC-3, AC-4 satisfied + + + + + + +## DO NOT CHANGE +- Logika `mapItems()` poza wywolaniem `extractPersonalization()` +- Struktura bazy danych (kolumna `personalization` juz istnieje) +- Widok wyswietlania personalizacji (show.php) +- ShopproOrdersSyncService, ShopproApiClient + +## SCOPE LIMITS +- Tylko zmiana parsera - bez migracji DB +- Bez zmian w widoku (wyswietlanie juz dziala) +- Bez resyncu wszystkich zamowien (to recznie po deploy) + + + + +Before declaring plan complete: +- [ ] PHP syntax OK: `php -l src/Modules/Settings/ShopproOrderMapper.php` +- [ ] Zamowienie 11776 po resync ma personalization = "Woreczek jutowy: Nie" +- [ ] Zamowienia z samym custom_fields dzialaja jak dotychczas (brak regresji) +- [ ] Zamowienia z obu polami maja polaczone dane + + + +- extractPersonalization() czyta zarowno attributes jak i custom_fields +- Istniejace zamowienia z custom_fields nie tracą danych +- Pole attributes jest poprawnie parsowane (HTML stripped, br -> newline) + + + +After completion, create `.paul/phases/71-attributes-import/71-01-SUMMARY.md` + diff --git a/.paul/phases/71-attributes-import/71-01-SUMMARY.md b/.paul/phases/71-attributes-import/71-01-SUMMARY.md new file mode 100644 index 0000000..f71cff9 --- /dev/null +++ b/.paul/phases/71-attributes-import/71-01-SUMMARY.md @@ -0,0 +1,101 @@ +--- +phase: 71-attributes-import +plan: 01 +subsystem: api +tags: [shoppro, sync, personalization, attributes] + +requires: + - phase: 63-order-item-personalization + provides: personalization column and extractPersonalization method +provides: + - Import pola attributes z API shopPRO do personalizacji produktow +affects: [] + +tech-stack: + added: [] + patterns: [multi-field extraction loop in extractPersonalization] + +key-files: + created: [] + modified: [src/Modules/Settings/ShopproOrderMapper.php] + +key-decisions: + - "attributes first, custom_fields second — kolejnosc laczenia pol" + - "Jednorazowa naprawa 36 istniejacych pozycji w bazie produkcyjnej" + +patterns-established: [] + +duration: 15min +started: 2026-04-07T12:00:00Z +completed: 2026-04-07T12:15:00Z +--- + +# Phase 71 Plan 01: Attributes Import Summary + +**Rozszerzenie extractPersonalization() o pole `attributes` z API shopPRO — atrybuty produktow (kolor, wariant, woreczek jutowy) sa teraz importowane obok custom_fields.** + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~15min | +| Tasks | 1 completed | +| Files modified | 1 | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: Atrybuty importowane do personalizacji | Pass | "Woreczek jutowy: Nie" poprawnie parsowane | +| AC-2: Polaczenie attributes + custom_fields | Pass | Oba pola laczone separatorem \n | +| AC-3: Puste pola bez pustych linii | Pass | Puste attributes pomijane | +| AC-4: Oba puste zwracaja null | Pass | Zachowanie kompatybilne wstecz | + +## Accomplishments + +- Rozszerzono `extractPersonalization()` o iteracje po `['attributes', 'custom_fields']` +- Naprawiono 36 istniejacych pozycji w bazie produkcyjnej jednorazowym skryptem +- Zweryfikowano 0 brakujacych pozycji po naprawie (pelne pokrycie) + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `src/Modules/Settings/ShopproOrderMapper.php` | Modified | extractPersonalization() czyta attributes + custom_fields | + +## Decisions Made + +| Decision | Rationale | Impact | +|----------|-----------|--------| +| attributes przed custom_fields | Atrybuty to metadane produktu (wariant), personalizacja to tresc klienta — logiczna kolejnosc | Sposob wyswietlania w UI | +| Jednorazowa naprawa danych w DB | payload_json przechowuje oryginalne dane — mozna przeliczyc personalizacje | 36 pozycji naprawionych bez resyncu | + +## Deviations from Plan + +### Summary + +| Type | Count | Impact | +|------|-------|--------| +| Scope additions | 1 | Pozytywny — naprawiono dane historyczne | + +**Dodatkowa naprawa:** Poza planem wykonano jednorazowy update 36 istniejacych pozycji w bazie produkcyjnej, ktore mialy attributes w payload_json ale pusta personalizacje. Nie wymagalo zmian w kodzie. + +## Issues Encountered + +None + +## Next Phase Readiness + +**Ready:** +- Nowe zamowienia beda importowane z pelna personalizacja (po deploy) +- Historyczne dane juz naprawione + +**Concerns:** +- Kod wymaga deploy na serwer (FTP) aby nowe importy dzialaly poprawnie + +**Blockers:** +- None + +--- +*Phase: 71-attributes-import, Plan: 01* +*Completed: 2026-04-07* diff --git a/DOCS/TECH_CHANGELOG.md b/DOCS/TECH_CHANGELOG.md index 8ea9995..63727a6 100644 --- a/DOCS/TECH_CHANGELOG.md +++ b/DOCS/TECH_CHANGELOG.md @@ -1,5 +1,12 @@ # Tech Changelog +## 2026-04-07 (Phase 71 - Attributes Import, Plan 01) +- `ShopproOrderMapper::extractPersonalization()`: rozszerzono o odczyt pola `attributes` z API shopPRO. + - Metoda iteruje po `['attributes', 'custom_fields']` i laczy niepuste wyniki separatorem `\n`. + - Atrybuty produktu (kolor, wariant, woreczek jutowy itp.) sa teraz importowane obok personalizacji. +- Jednorazowa naprawa 36 istniejacych pozycji w bazie produkcyjnej (przeliczenie personalizacji z payload_json). +- Brak zmian schematu bazy danych. + ## 2026-04-04 (Email templates - split list/form view) - `EmailTemplateController`: - dodano osobne endpointy widokowe `create()` i `edit()` dla formularza szablonu, diff --git a/src/Modules/Settings/ShopproOrderMapper.php b/src/Modules/Settings/ShopproOrderMapper.php index c795357..9ea6e3c 100644 --- a/src/Modules/Settings/ShopproOrderMapper.php +++ b/src/Modules/Settings/ShopproOrderMapper.php @@ -589,16 +589,21 @@ final class ShopproOrderMapper */ private function extractPersonalization(array $row): ?string { - $raw = $this->readPath($row, ['custom_fields']); - if ($raw === null || $raw === '' || $raw === false) { - return null; + $parts = []; + foreach (['attributes', 'custom_fields'] as $field) { + $raw = $this->readPath($row, [$field]); + if ($raw === null || $raw === '' || $raw === false) { + continue; + } + $text = str_replace(['
', '
', '
'], "\n", (string) $raw); + $text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $text = trim($text); + if ($text !== '') { + $parts[] = $text; + } } - $text = str_replace(['
', '
', '
'], "\n", (string) $raw); - $text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8'); - $text = trim($text); - - return $text !== '' ? $text : null; + return $parts !== [] ? implode("\n", $parts) : null; } /**