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] 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)
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
|
||||
## 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,
|
||||
|
||||
@@ -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(['<br>', '<br/>', '<br />'], "\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(['<br>', '<br/>', '<br />'], "\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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user