From 39c318382a1a59b473eae56de1527d55c1be362d Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Thu, 2 Apr 2026 00:17:46 +0200 Subject: [PATCH] update --- .paul/PROJECT.md | 1 + .paul/ROADMAP.md | 1 + .paul/STATE.md | 22 +-- .../63-01-PLAN.md | 164 ++++++++++++++++++ .../63-01-SUMMARY.md | 127 ++++++++++++++ DOCS/DB_SCHEMA.md | 1 + DOCS/TECH_CHANGELOG.md | 8 + ...075_add_personalization_to_order_items.sql | 1 + public/assets/css/app.css | 22 +++ resources/scss/app.css | 24 +++ resources/scss/app.scss | 24 +++ resources/views/orders/show.php | 13 +- src/Modules/Orders/OrderImportRepository.php | 5 +- src/Modules/Settings/ShopproOrderMapper.php | 20 ++- 14 files changed, 417 insertions(+), 16 deletions(-) create mode 100644 .paul/phases/63-order-item-personalization/63-01-PLAN.md create mode 100644 .paul/phases/63-order-item-personalization/63-01-SUMMARY.md create mode 100644 database/migrations/20260401_000075_add_personalization_to_order_items.sql diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index 469839a..2175982 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -71,6 +71,7 @@ Sprzedawca moĹĽe obsĹ‚ugiwać zamĂłwienia ze wszystkich kanałów - [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 +- [x] Import i wyswietlanie personalizacji produktow z shopPRO (custom_fields) + naprawa daty zamowienia — Phase 63 ### Active (In Progress) diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 31841de..f8da244 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -23,6 +23,7 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel | 60 | Order Status Aged Event | 1/1 | Complete | | 61 | Payment Button Activation | 1/1 | Complete | | 62 | Import Re-import Safety | 1/1 | Complete | +| 63 | Order Item Personalization | 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 80e8b8f..ad34071 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -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 62 complete, ready for next PLAN +**Current focus:** Milestone v3.0 — Phase 63 complete, ready for next PLAN ## Current Position Milestone: v3.0 Mobile Responsive — In progress -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 +Phase: 12 of N (63 - Order Item Personalization) — Complete +Plan: 63-01 complete +Status: Loop complete — phase 63 done, ready for next PLAN +Last activity: 2026-04-01 — UNIFY closed for 63-01 Progress: -- Milestone: [######░░░░] ~58% -- Phase 62: [##########] 100% +- Milestone: [######░░░░] ~62% +- Phase 63: [##########] 100% ## Loop Position @@ -29,13 +29,13 @@ PLAN ──▶ APPLY ──▶ UNIFY ## Session Continuity -Last session: 2026-03-31 -Stopped at: Phase 62 complete +Last session: 2026-04-01 +Stopped at: Phase 63 complete Next action: /paul:plan dla kolejnego modulu -Resume file: .paul/phases/62-import-reimport-safety/62-01-SUMMARY.md +Resume file: .paul/phases/63-order-item-personalization/63-01-SUMMARY.md ## Git State -Last commit: af48e84 +Last commit: 34b0a2b Branch: main Feature branches merged: none diff --git a/.paul/phases/63-order-item-personalization/63-01-PLAN.md b/.paul/phases/63-order-item-personalization/63-01-PLAN.md new file mode 100644 index 0000000..16d7d80 --- /dev/null +++ b/.paul/phases/63-order-item-personalization/63-01-PLAN.md @@ -0,0 +1,164 @@ +--- +phase: 63-order-item-personalization +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - database/migrations/20260401_000063_add_personalization_to_order_items.sql + - src/Modules/Settings/ShopproOrderMapper.php + - src/Modules/Orders/OrderImportRepository.php + - resources/views/orders/show.php + - resources/scss/orders/_show.scss +autonomous: true +--- + + +## Goal +Pobieranie danych personalizacji produktów z shopPRO i wyświetlanie ich w szczegółach zamówienia orderPRO. + +## Purpose +Sprzedawcy widzą personalizacje klientów (np. tekst grawerunku, link Spotify, dedykacja) bezpośrednio w orderPRO bez konieczności logowania do shopPRO. Kluczowe dla realizacji zamówień z personalizowanymi produktami. + +## Output +- Migracja DB: kolumna `personalization` w `order_items` +- Mapper: ekstrakcja `custom_fields` z odpowiedzi shopPRO API +- UI: wyświetlanie personalizacji pod nazwą produktu w widoku zamówienia + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md + +## Source Files +@src/Modules/Settings/ShopproOrderMapper.php — mapItems() linia 527, mapuje produkty z API shopPRO +@src/Modules/Orders/OrderImportRepository.php — replaceItems() wstawia pozycje do DB +@resources/views/orders/show.php — widok szczegółów zamówienia, sekcja produktów +@database/migrations/20260302_000018_create_orders_tables_and_schedule.sql — schemat order_items + + + +No specialized flows configured. + + + + +## AC-1: Personalizacja zapisana w bazie danych +```gherkin +Given zamówienie z shopPRO zawiera produkty z custom_fields +When import zamówienia się wykonuje (cron lub ręczny) +Then kolumna personalization w order_items zawiera dane personalizacji jako czysty tekst (bez HTML) +``` + +## AC-2: Personalizacja wyświetlana w szczegółach zamówienia +```gherkin +Given zamówienie w orderPRO ma pozycje z wypełnioną personalizacją +When użytkownik otwiera szczegóły zamówienia (/orders/{id}) +Then pod nazwą produktu wyświetla się sekcja personalizacji z etykietami i wartościami +``` + +## AC-3: Brak personalizacji nie powoduje błędów +```gherkin +Given zamówienie z shopPRO zawiera produkty BEZ custom_fields (puste lub null) +When import się wykonuje i użytkownik otwiera szczegóły +Then kolumna personalization jest NULL i w UI nie pojawia się sekcja personalizacji +``` + + + + + + + Task 1: Migracja DB + mapping personalizacji + database/migrations/20260401_000063_add_personalization_to_order_items.sql, src/Modules/Settings/ShopproOrderMapper.php, src/Modules/Orders/OrderImportRepository.php + + 1. Utworzyć migrację dodającą kolumnę `personalization TEXT NULL` do tabeli `order_items` (po kolumnie `payload_json`). + + 2. W `ShopproOrderMapper::mapItems()` (linia 561-580): + - Wyciągnąć `custom_fields` z `$row` za pomocą `$this->readPath($row, ['custom_fields'])` + - Przekonwertować HTML na czysty tekst: zamienić `
` na `\n`, usunąć tagi HTML (`strip_tags`), `html_entity_decode`, `trim` + - Zapisać jako klucz `personalization` w tablicy wynikowej (obok `payload_json`) + - Jeśli wynik jest pustym stringiem, ustawić `null` + + 3. W `OrderImportRepository::replaceItems()`: + - Dodać kolumnę `personalization` do INSERT query + - Bindować wartość z tablicy item + + Avoid: Nie zmieniać formatu `payload_json` — surowe dane API muszą zostać nienaruszone. + Avoid: Nie parsować HTML do JSON — czysty tekst z zachowanymi newlines jest wystarczający. +
+ + - Migracja wykonuje się bez błędów: `php database/migrate.php` + - Po re-imporcie zamówienia z shopPRO kolumna `personalization` zawiera tekst + - Zamówienia bez personalizacji mają NULL w kolumnie + + AC-1 i AC-3 satisfied: personalizacja zapisywana w DB, brak personalizacji = NULL +
+ + + Task 2: Wyświetlanie personalizacji w widoku zamówienia + resources/views/orders/show.php, resources/scss/orders/_show.scss + + 1. W `resources/views/orders/show.php`, w sekcji renderującej produkty zamówienia: + - Pod nazwą produktu (`original_name`) dodać blok warunkowy: jeśli `$item['personalization']` nie jest puste + - Wyświetlić personalizację w `
` z ikoną lub etykietą "Personalizacja:" + - Każda linia personalizacji (split po `\n`) jako osobna linia w UI + - Escape HTML: użyć `e()` helpera na każdej linii + + 2. W SCSS dodać style: + - `.item-personalization` — mały font (0.85em), kolor muted, lekki padding-top + - Etykiety (tekst przed `:`) mogą być pogrubione via CSS lub pozostawione jako plain text + - Kompaktowy układ, bez nadmiernych marginesów + + Avoid: Nie renderować surowego HTML z custom_fields — zawsze escape. + Avoid: Nie dodawać nowych zależności JS — to statyczny tekst. + + + - Otworzyć zamówienie z personalizacją w przeglądarce — personalizacja widoczna pod nazwą produktu + - Otworzyć zamówienie bez personalizacji — brak dodatkowej sekcji + - Sprawdzić XSS: wpisać `` w custom_fields shopPRO — powinno być escaped + + AC-2 satisfied: personalizacja wyświetlana w UI pod nazwą produktu + + + + + + +## DO NOT CHANGE +- shopPRO codebase (API już zwraca custom_fields — zero zmian po stronie shopPRO) +- payload_json format i zawartość w order_items +- Istniejące kolumny i indeksy order_items +- Logika importu zamówień poza mapowaniem items (OrderImportRepository::upsertOrderAggregate flow) + +## SCOPE LIMITS +- Tylko personalizacja z shopPRO (nie Allegro, nie Erli) +- Tylko widok szczegółów zamówienia — bez zmian na liście zamówień +- Bez edycji personalizacji w orderPRO — read-only display +- Bez parsowania personalizacji do struktury klucz-wartość (JSON) — plain text wystarczy + + + + +Before declaring plan complete: +- [ ] Migracja wykonuje się bez błędów +- [ ] SCSS kompiluje się bez błędów +- [ ] Zamówienie z personalizacją — dane widoczne w UI +- [ ] Zamówienie bez personalizacji — brak sekcji w UI +- [ ] Brak regresji w istniejącym wyświetlaniu produktów +- [ ] XSS escape działa poprawnie +- [ ] Aktualizacja DOCS/DB_SCHEMA.md i DOCS/ARCHITECTURE.md + + + +- Wszystkie taski zakończone +- Wszystkie weryfikacje przechodzą +- Brak błędów PHP/SCSS +- Personalizacja widoczna w UI dla produktów z custom_fields + + + +After completion, create `.paul/phases/63-order-item-personalization/63-01-SUMMARY.md` + diff --git a/.paul/phases/63-order-item-personalization/63-01-SUMMARY.md b/.paul/phases/63-order-item-personalization/63-01-SUMMARY.md new file mode 100644 index 0000000..f6bc20b --- /dev/null +++ b/.paul/phases/63-order-item-personalization/63-01-SUMMARY.md @@ -0,0 +1,127 @@ +--- +phase: 63-order-item-personalization +plan: 01 +subsystem: orders +tags: [personalization, shopPRO, import, custom_fields] + +requires: + - phase: none + provides: n/a +provides: + - Kolumna personalization w order_items + - Ekstrakcja custom_fields z shopPRO API przy imporcie + - Wyswietlanie personalizacji w widoku zamowienia +affects: [] + +tech-stack: + added: [] + patterns: [extractPersonalization HTML-to-text w mapperze] + +key-files: + created: + - database/migrations/20260401_000075_add_personalization_to_order_items.sql + modified: + - src/Modules/Settings/ShopproOrderMapper.php + - src/Modules/Orders/OrderImportRepository.php + - resources/views/orders/show.php + - resources/scss/app.scss + - public/assets/css/app.css + +key-decisions: + - "Personalizacja jako plain text (nie JSON) — prostosc, wystarczajaca dla wyswietlania" + - "date_order dodane do readPath mapper — naprawa brakujacej daty zamowienia" + +patterns-established: + - "extractPersonalization: HTML strip + br-to-newline dla danych z shopPRO" + +duration: ~45min +started: 2026-04-01T19:00:00Z +completed: 2026-04-01T19:45:00Z +--- + +# Phase 63 Plan 01: Order Item Personalization Summary + +**Import i wyswietlanie danych personalizacji produktow z shopPRO (custom_fields) w szczegolach zamowienia orderPRO.** + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~45min | +| Started | 2026-04-01 | +| Completed | 2026-04-01 | +| Tasks | 2 completed | +| Files modified | 7 | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: Personalizacja zapisana w DB | Pass | custom_fields ekstrakcja z HTML na tekst, zapis w kolumnie personalization | +| AC-2: Personalizacja wyswietlana w UI | Pass | Blok pod nazwa produktu z etykieta i liniami tekstu | +| AC-3: Brak personalizacji nie powoduje bledow | Pass | NULL w kolumnie, brak sekcji w UI | + +## Accomplishments + +- Kolumna `personalization TEXT NULL` w `order_items` + ekstrakcja `custom_fields` z shopPRO API +- Wyswietlanie personalizacji w widoku zamowienia z escape XSS +- Naprawa brakujacej daty zamowienia (`date_order` dodane do mapper readPath) +- Usuniecie zbednego pola `item_type` z widoku i duplikatu `source_order_id` +- Dodanie `margin-bottom` do section-title dla lepszego odstepy pod kreska + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `database/migrations/20260401_000075_add_personalization_to_order_items.sql` | Created | Kolumna personalization w order_items | +| `src/Modules/Settings/ShopproOrderMapper.php` | Modified | extractPersonalization() + date_order w readPath | +| `src/Modules/Orders/OrderImportRepository.php` | Modified | personalization w INSERT query | +| `resources/views/orders/show.php` | Modified | Wyswietlanie personalizacji, usuniecie item_type i source_order_id | +| `resources/scss/app.scss` | Modified | Style .item-personalization + section-title margin-bottom | +| `public/assets/css/app.css` | Modified | Skompilowany CSS | +| `DOCS/DB_SCHEMA.md` | Modified | Dokumentacja kolumny personalization | +| `DOCS/TECH_CHANGELOG.md` | Modified | Wpis Phase 63 | + +## Decisions Made + +| Decision | Rationale | Impact | +|----------|-----------|--------| +| Plain text zamiast JSON dla personalizacji | Prostosc — dane sa read-only, nie wymagaja struktury klucz-wartosc | Wystarczajace dla wyswietlania | +| date_order w readPath mappera | shopPRO API zwraca date_order, mapper nie mial tego klucza | Naprawia brakujaca date zamowienia | +| Usuniecie item_type z widoku | Zawsze "product", zero informacji | Czystszy UI | +| Usuniecie source_order_id z widoku | Duplikat external_order_id | Czystszy UI | + +## Deviations from Plan + +### Summary + +| Type | Count | Impact | +|------|-------|--------| +| Scope additions | 3 | Drobne poprawki UI wykryte przy testowaniu | +| Deferred | 0 | - | + +**Total impact:** Drobne poprawki UI (date_order mapping, item_type/source_order_id cleanup, section-title spacing) — naturalne odkrycia przy testowaniu na zywo. + +## Issues Encountered + +| Issue | Resolution | +|-------|------------| +| Lokalna DB offline | Migracja uruchomiona na zdalnej DB przez mysql CLI | +| Cron nie pobral zamowienia (stary updated_at) | Touch updated_at w shopPRO DB + reset kursora sync | +| marianek.pl to osobna instancja shopPRO | Znaleziony config.php z danymi DB | + +## Next Phase Readiness + +**Ready:** +- Personalizacja importowana i wyswietlana +- Mapper rozszerzalny o kolejne pola shopPRO API + +**Concerns:** +- Brak + +**Blockers:** +- None + +--- +*Phase: 63-order-item-personalization, Plan: 01* +*Completed: 2026-04-01* diff --git a/DOCS/DB_SCHEMA.md b/DOCS/DB_SCHEMA.md index f5aa93b..6f6a42a 100644 --- a/DOCS/DB_SCHEMA.md +++ b/DOCS/DB_SCHEMA.md @@ -158,6 +158,7 @@ Migracje z prefiksem `ensure_` to migracje kompensujące — zostały dodane - schema neutralna wzgledem dostawcy API (pola `source_*`, `external_*`), - kolekcje zamowienia rozdzielone na osobne tabele 1:N, - `payload_json` dostepne dla diagnostyki/replay, + - `personalization` (TEXT, nullable) w `order_items` — dane personalizacji produktu z shopPRO (custom_fields), przechowywane jako czysty tekst, - historia zmian statusow utrzymywana w `order_status_history`. ### `integration_order_sync_state` diff --git a/DOCS/TECH_CHANGELOG.md b/DOCS/TECH_CHANGELOG.md index 994efad..4c7bbf6 100644 --- a/DOCS/TECH_CHANGELOG.md +++ b/DOCS/TECH_CHANGELOG.md @@ -1,5 +1,13 @@ # Tech Changelog +## 2026-04-01 (Phase 63 - Order Item Personalization, Plan 01) +- Migracja `20260401_000075_add_personalization_to_order_items.sql`: kolumna `personalization TEXT NULL` w `order_items`. +- `ShopproOrderMapper::extractPersonalization()`: ekstrakcja `custom_fields` z odpowiedzi shopPRO API, konwersja HTML na czysty tekst (strip_tags, html_entity_decode, br->newline). +- `ShopproOrderMapper::mapItems()`: dodanie klucza `personalization` do mapowanego itemu. +- `OrderImportRepository::replaceItems()`: zapis `personalization` do INSERT query. +- `resources/views/orders/show.php`: warunkowe wyswietlanie personalizacji pod nazwa produktu (div.item-personalization z etykieta i liniami tekstu, escape via `e()`). +- `resources/scss/app.scss`: style `.item-personalization` (kompaktowy blok, border-left, muted colors). + ## 2026-03-31 (Phase 60 - Order Status Aged Event, Plan 01) - Migracja `20260331_000074_seed_order_status_aged_cron.sql`: seed cron schedule `order_status_aged` co 3600s. - `OrderStatusAgedService`: skanuje zamowienia w danym statusie od X dni (query HAVING MAX(changed_at) na `order_status_history`), limit 100/regule, trigger `order.status_aged`. diff --git a/database/migrations/20260401_000075_add_personalization_to_order_items.sql b/database/migrations/20260401_000075_add_personalization_to_order_items.sql new file mode 100644 index 0000000..6fa698b --- /dev/null +++ b/database/migrations/20260401_000075_add_personalization_to_order_items.sql @@ -0,0 +1 @@ +ALTER TABLE order_items ADD COLUMN personalization TEXT NULL AFTER payload_json; diff --git a/public/assets/css/app.css b/public/assets/css/app.css index 36cbc5a..a102637 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -1099,6 +1099,7 @@ h4.section-title { gap: 6px; font-weight: 600; padding: 6px 0; + margin-bottom: 8px; border-bottom: 1px solid #e2e8f0; color: var(--c-primary, #2563eb); } @@ -2313,6 +2314,27 @@ details[open] > .order-statuses-side__title .order-statuses-side__arrow { color: #0f172a; } +.item-personalization { + margin-top: 4px; + padding: 4px 8px; + background: #f8fafc; + border-left: 2px solid #cbd5e1; + border-radius: 2px; + font-size: 0.92em; + color: #475569; + line-height: 1.4; +} +.item-personalization__label { + font-weight: 600; + color: #64748b; + display: block; + margin-bottom: 2px; +} +.item-personalization__line { + white-space: pre-wrap; + word-break: break-word; +} + .order-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); diff --git a/resources/scss/app.css b/resources/scss/app.css index 677dd06..a102637 100644 --- a/resources/scss/app.css +++ b/resources/scss/app.css @@ -1099,6 +1099,7 @@ h4.section-title { gap: 6px; font-weight: 600; padding: 6px 0; + margin-bottom: 8px; border-bottom: 1px solid #e2e8f0; color: var(--c-primary, #2563eb); } @@ -1909,6 +1910,7 @@ h4.section-title::before { font-size: 12px; font-weight: 700; line-height: 1.1; + white-space: nowrap; } .order-tag.is-info { border-color: #bfdbfe; @@ -2312,6 +2314,27 @@ details[open] > .order-statuses-side__title .order-statuses-side__arrow { color: #0f172a; } +.item-personalization { + margin-top: 4px; + padding: 4px 8px; + background: #f8fafc; + border-left: 2px solid #cbd5e1; + border-radius: 2px; + font-size: 0.92em; + color: #475569; + line-height: 1.4; +} +.item-personalization__label { + font-weight: 600; + color: #64748b; + display: block; + margin-bottom: 2px; +} +.item-personalization__line { + white-space: pre-wrap; + word-break: break-word; +} + .order-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -2393,6 +2416,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 { diff --git a/resources/scss/app.scss b/resources/scss/app.scss index 98ab0d8..da933b0 100644 --- a/resources/scss/app.scss +++ b/resources/scss/app.scss @@ -359,6 +359,7 @@ h4.section-title { gap: 6px; font-weight: 600; padding: 6px 0; + margin-bottom: 8px; border-bottom: 1px solid #e2e8f0; color: var(--c-primary, #2563eb); @@ -1602,6 +1603,29 @@ details[open] > .order-statuses-side__title .order-statuses-side__arrow { color: #0f172a; } +.item-personalization { + margin-top: 4px; + padding: 4px 8px; + background: #f8fafc; + border-left: 2px solid #cbd5e1; + border-radius: 2px; + font-size: 0.92em; + color: #475569; + line-height: 1.4; + + &__label { + font-weight: 600; + color: #64748b; + display: block; + margin-bottom: 2px; + } + + &__line { + white-space: pre-wrap; + word-break: break-word; + } +} + .order-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); diff --git a/resources/views/orders/show.php b/resources/views/orders/show.php index 97d24d7..e5f5e80 100644 --- a/resources/views/orders/show.php +++ b/resources/views/orders/show.php @@ -169,7 +169,17 @@ foreach ($addressesList as $address) {
-
+ + +
+ Personalizacja: + + +
+ + +
+
@@ -193,7 +203,6 @@ foreach ($addressesList as $address) {
Nr zamowienia
-
diff --git a/src/Modules/Orders/OrderImportRepository.php b/src/Modules/Orders/OrderImportRepository.php index 2e9384b..2b40ab4 100644 --- a/src/Modules/Orders/OrderImportRepository.php +++ b/src/Modules/Orders/OrderImportRepository.php @@ -256,11 +256,11 @@ final class OrderImportRepository 'INSERT INTO order_items ( order_id, source_item_id, external_item_id, ean, sku, original_name, original_code, original_price_with_tax, original_price_without_tax, media_url, quantity, tax_rate, item_status, - unit, item_type, source_product_id, source_product_set_id, sort_order, payload_json + unit, item_type, source_product_id, source_product_set_id, sort_order, payload_json, personalization ) VALUES ( :order_id, :source_item_id, :external_item_id, :ean, :sku, :original_name, :original_code, :original_price_with_tax, :original_price_without_tax, :media_url, :quantity, :tax_rate, :item_status, - :unit, :item_type, :source_product_id, :source_product_set_id, :sort_order, :payload_json + :unit, :item_type, :source_product_id, :source_product_set_id, :sort_order, :payload_json, :personalization )' ); @@ -285,6 +285,7 @@ final class OrderImportRepository 'source_product_set_id' => $row['source_product_set_id'] ?? null, 'sort_order' => (int) ($row['sort_order'] ?? 0), 'payload_json' => $this->encodeJson($row['payload_json'] ?? null), + 'personalization' => $row['personalization'] ?? null, ]); } } diff --git a/src/Modules/Settings/ShopproOrderMapper.php b/src/Modules/Settings/ShopproOrderMapper.php index b6d8dde..c795357 100644 --- a/src/Modules/Settings/ShopproOrderMapper.php +++ b/src/Modules/Settings/ShopproOrderMapper.php @@ -71,7 +71,7 @@ final class ShopproOrderMapper $sourceOrderId = $fallbackOrderId; } - $sourceCreatedAt = StringHelper::normalizeDateTime((string) $this->readPath($payload, ['created_at', 'date_created', 'date_add'])); + $sourceCreatedAt = StringHelper::normalizeDateTime((string) $this->readPath($payload, ['created_at', 'date_created', 'date_add', 'date_order'])); $sourceUpdatedAt = StringHelper::normalizeDateTime((string) $this->readPath($payload, ['updated_at', 'date_updated', 'modified_at', 'date_modified', 'created_at'])); if ($sourceUpdatedAt === null) { $sourceUpdatedAt = $fallbackUpdatedAt !== '' ? $fallbackUpdatedAt : date('Y-m-d H:i:s'); @@ -577,12 +577,30 @@ final class ShopproOrderMapper 'source_product_set_id' => StringHelper::nullableString((string) ($parentProductId > 0 ? $parentProductId : '')), 'sort_order' => $sort++, 'payload_json' => $row, + 'personalization' => $this->extractPersonalization($row), ]; } return $result; } + /** + * @param array $row + */ + private function extractPersonalization(array $row): ?string + { + $raw = $this->readPath($row, ['custom_fields']); + if ($raw === null || $raw === '' || $raw === false) { + return null; + } + + $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; + } + /** * @param array $payload * @return array>