feat(attributes-import): import product attributes from shopPRO API into personalization
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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] 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] 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] 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
|
- [ ] Eliminacja zduplikowanego kodu: SslCertificateResolver, ToggleableRepositoryTrait, RedirectPathResolver, ReceiptService — Phase 68
|
||||||
|
|
||||||
### Active (In Progress)
|
### Active (In Progress)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel
|
|||||||
| 68 | Code Deduplication Refactor | 0/2 | Planning |
|
| 68 | Code Deduplication Refactor | 0/2 | Planning |
|
||||||
| 69 | Allegro Tracking English Statuses | 1/1 | Complete |
|
| 69 | Allegro Tracking English Statuses | 1/1 | Complete |
|
||||||
| 70 | Receipt Shipping Cost | 1/1 | Complete |
|
| 70 | Receipt Shipping Cost | 1/1 | Complete |
|
||||||
|
| 71 | Attributes Import | 1/1 | Complete |
|
||||||
| TBD | Mobile Orders List | - | Not started |
|
| TBD | Mobile Orders List | - | Not started |
|
||||||
| TBD | Mobile Order Details | - | Not started |
|
| TBD | Mobile Order Details | - | Not started |
|
||||||
| TBD | Mobile Settings | - | Not started |
|
| TBD | Mobile Settings | - | Not started |
|
||||||
|
|||||||
@@ -2,22 +2,22 @@
|
|||||||
|
|
||||||
## Project Reference
|
## 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.
|
**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
|
## Current Position
|
||||||
|
|
||||||
Milestone: v3.0 Mobile Responsive - In progress
|
Milestone: v3.0 Mobile Responsive - In progress
|
||||||
Phase: 70 (Receipt Shipping Cost) — Complete
|
Phase: 71 (Attributes Import) — Complete
|
||||||
Plan: 70-01 unified
|
Plan: 71-01 unified
|
||||||
Status: Loop complete, ready for next PLAN
|
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:
|
Progress:
|
||||||
- Milestone: [#######...] ~72%
|
- Milestone: [#######...] ~74%
|
||||||
- Phase 70: [##########] 100%
|
- Phase 71: [##########] 100%
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
@@ -29,12 +29,12 @@ PLAN --> APPLY --> UNIFY
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-06
|
Last session: 2026-04-07
|
||||||
Stopped at: Plan 70-01 unified
|
Stopped at: Plan 71-01 unified
|
||||||
Next action: Run /paul:plan for the next prioritized phase
|
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
|
## Git State
|
||||||
|
|
||||||
Last commit: 0e7ee95
|
Last commit: 278f44b
|
||||||
Branch: main
|
Branch: main
|
||||||
|
|||||||
120
.paul/phases/71-attributes-import/71-01-PLAN.md
Normal file
120
.paul/phases/71-attributes-import/71-01-PLAN.md
Normal file
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## 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`.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/ROADMAP.md
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@src/Modules/Settings/ShopproOrderMapper.php (linie 590-602 - extractPersonalization)
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Atrybuty produktu importowane do personalizacji
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie w shopPRO ma produkt z polem attributes = "<b>Woreczek jutowy</b>: 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 = "<b>Kolor tekstu</b>: Bialy<br><b>Zakretka</b>: Zlota" i custom_fields = "<b>Imiona</b>: 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 = "<b>Imie</b>: 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)
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Rozszerzenie extractPersonalization o pole attributes</name>
|
||||||
|
<files>src/Modules/Settings/ShopproOrderMapper.php</files>
|
||||||
|
<action>
|
||||||
|
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.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
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"
|
||||||
|
</verify>
|
||||||
|
<done>AC-1, AC-2, AC-3, AC-4 satisfied</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
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
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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)
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.paul/phases/71-attributes-import/71-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
101
.paul/phases/71-attributes-import/71-01-SUMMARY.md
Normal file
101
.paul/phases/71-attributes-import/71-01-SUMMARY.md
Normal file
@@ -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*
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
# Tech Changelog
|
# 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)
|
## 2026-04-04 (Email templates - split list/form view)
|
||||||
- `EmailTemplateController`:
|
- `EmailTemplateController`:
|
||||||
- dodano osobne endpointy widokowe `create()` i `edit()` dla formularza szablonu,
|
- dodano osobne endpointy widokowe `create()` i `edit()` dla formularza szablonu,
|
||||||
|
|||||||
@@ -589,16 +589,21 @@ final class ShopproOrderMapper
|
|||||||
*/
|
*/
|
||||||
private function extractPersonalization(array $row): ?string
|
private function extractPersonalization(array $row): ?string
|
||||||
{
|
{
|
||||||
$raw = $this->readPath($row, ['custom_fields']);
|
$parts = [];
|
||||||
|
foreach (['attributes', 'custom_fields'] as $field) {
|
||||||
|
$raw = $this->readPath($row, [$field]);
|
||||||
if ($raw === null || $raw === '' || $raw === false) {
|
if ($raw === null || $raw === '' || $raw === false) {
|
||||||
return null;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$text = str_replace(['<br>', '<br/>', '<br />'], "\n", (string) $raw);
|
$text = str_replace(['<br>', '<br/>', '<br />'], "\n", (string) $raw);
|
||||||
$text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
$text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
$text = trim($text);
|
$text = trim($text);
|
||||||
|
if ($text !== '') {
|
||||||
|
$parts[] = $text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $text !== '' ? $text : null;
|
return $parts !== [] ? implode("\n", $parts) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user