Plan 02-03: Customization save + success modal (5/5 AC)
- 26-field squaremeter POST payload (verbose PL dim, qty_alt/qty_alth)
- Chain POST /module/ps_shoppingcart/ajax -> Bootstrap #blockcart-modal
- Critical fix: moved {/block} so inline script actually renders
- __p02p02InFlight re-entrancy guard
Plan 02-04: Live cena per-sqm label obok "Dodaj do koszyka" (5/5 AC)
- .p02p04-total-price label, gorna .current-price static
- Separate __p02p04Bound + setInterval reconciliation
- Poll-retry prestashop.on registration
Plan 02-05: Struktura materialu w POST payload (4/4 AC)
- Enumerate [name^="group["] spoza formy, doklej do payload
- Fix: group_5 select w .product-bar-box nie trafial do koszyka
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
249 lines
11 KiB
Markdown
249 lines
11 KiB
Markdown
---
|
||
phase: 02-product-actions-fixes
|
||
plan: 05
|
||
type: execute
|
||
wave: 1
|
||
depends_on: ["02-03"]
|
||
files_modified:
|
||
- themes/ayon/assets/js/custom.js
|
||
- themes/ayon/templates/catalog/product.tpl
|
||
autonomous: false
|
||
delegation: off
|
||
---
|
||
|
||
<objective>
|
||
## Goal
|
||
Wybrana struktura materiału (`<select id="group_5" name="group[5]">` — atrybut „Tekstura materiału") **zapisuje się do koszyka** w nowym layoucie. Obecnie select jest poza formą `#add-to-cart-or-refresh` (w `.product-bar-box`), wiec `$form.serialize()` go nie łapie → POST payload nie zawiera `group[5]` → PS zapisuje wrong attribute combination.
|
||
|
||
## Purpose
|
||
Phase 02 complete ale 02-03 UNIFY pominął że niektóre PS attribute group inputs są **poza formą** (identyczny pattern jak button+qty z Plan 02-02). Kolor (`group[4]`) działa bo radios są w formie. Struktura (`group[5]`) nie — select jest w product-bar, poza formą. Bug production-blocking dla milestone (klient kupuje złą strukturę).
|
||
|
||
## Output
|
||
- Zmiana w POST payload builder: enumeruj WSZYSTKIE `[name^="group"]` poza `#add-to-cart-or-refresh` i dołącz do payload.
|
||
- Zmiany w custom.js + inline mirror w product.tpl (identyczne, identity semantics).
|
||
- Zero zmian markupie templatu (nie przesuwamy selecta do formy — Bootstrap grid constraint jak przy qty/button).
|
||
- Summary: `.paul/phases/02-product-actions-fixes/02-05-SUMMARY.md`.
|
||
</objective>
|
||
|
||
<context>
|
||
## Project Context
|
||
@.paul/PROJECT.md
|
||
@.paul/ROADMAP.md
|
||
@.paul/STATE.md
|
||
|
||
## Prior Work (direct dependency)
|
||
@.paul/phases/02-product-actions-fixes/02-03-SUMMARY.md
|
||
# Plan 02-03 ustalił pattern: $form.serialize() + dodatkowe pola (qty, sq fields)
|
||
# encodeURIComponent'owane w payload. Ten plan rozszerza payload o group[N]
|
||
# inputy spoza formy.
|
||
|
||
## Source Files
|
||
@themes/ayon/assets/js/custom.js
|
||
# Line ~1083: payload construction w add-to-cart handler:
|
||
# var payload = $form.serialize() + '&qty=' + ... + '&' + sqFields + ...
|
||
# Tutaj wstrzyknąć enumerację external group inputs.
|
||
@themes/ayon/templates/catalog/product.tpl
|
||
# Line ~853: inline mirror ma identyczny payload builder (jQuery).
|
||
</context>
|
||
|
||
<skills>
|
||
## Required Skills
|
||
|
||
| Skill | Priority | When to Invoke | Loaded? |
|
||
|-------|----------|----------------|---------|
|
||
| Playwright MCP (`mcp__plugin_playwright_playwright__*`) | required | Task 3 live verification + payload capture | ✓ |
|
||
|
||
**BLOCKING:** Playwright MCP wymagany w Task 3 — inspect POST payload + cart contents.
|
||
</skills>
|
||
|
||
<acceptance_criteria>
|
||
|
||
## AC-1: `group[5]` (struktura) w POST payload
|
||
```gherkin
|
||
Given user na stronie produktu w NEW layoucie (IP 89.69.31.86)
|
||
And select `#group_5` ma value "10" (inna struktura niż default value "9")
|
||
And piece configured (50×50, checkbox-piece checked)
|
||
When user klika "Dodaj do koszyka"
|
||
Then POST /pl/koszyk zawiera `group[5]=10` w body
|
||
And (wszystkie inne `group[N]` spoza formy tez są w body — future-proof dla kolejnych attribute groups)
|
||
```
|
||
|
||
## AC-2: Cart wyświetla wybraną strukturę
|
||
```gherkin
|
||
Given user zmieni select `#group_5` na strukturę "X" (nie default)
|
||
And doda produkt do koszyka
|
||
When blockcart modal się otworzy (Plan 02-03)
|
||
Then modal (lub preview .blockcart content) pokazuje nazwę struktury "X"
|
||
Or (jeśli modal nie pokazuje atrybutów) cart page `/pl/koszyk` pokazuje strukturę "X"
|
||
```
|
||
|
||
## AC-3: Kolor (`group[4]`) nadal działa
|
||
```gherkin
|
||
Given user nie zmieni nic, zaakceptuje default color + default structure
|
||
When POST firuje
|
||
Then payload zawiera zarowno `group[4]=5` (default color) jak i `group[5]=9` (default structure)
|
||
And cart pokazuje correct combination
|
||
```
|
||
|
||
## AC-4: Zero regression OLD layout
|
||
```gherkin
|
||
Given strona produktu poza IP 89.69.31.86 (OLD layout)
|
||
When user klika "Dodaj do koszyka"
|
||
Then PS core handler działa jak dotychczas (zero zmian w flow OLD)
|
||
And nie ma JS errors w console
|
||
```
|
||
|
||
</acceptance_criteria>
|
||
|
||
<tasks>
|
||
|
||
<task type="auto">
|
||
<name>Task 1: Inject external group[N] inputs do POST payload</name>
|
||
<files>themes/ayon/assets/js/custom.js, themes/ayon/templates/catalog/product.tpl</files>
|
||
<action>
|
||
W custom.js (add-to-cart handler, obecna linia gdzie payload jest budowany):
|
||
|
||
Znajdź linię:
|
||
```js
|
||
var payload = $form.serialize() + '&qty=' + encodeURIComponent(qty) + '&' + sqFields + '&add=1&action=update';
|
||
```
|
||
|
||
Dodaj PRZED tą linią (lub nad budowaniem payload):
|
||
```js
|
||
// Plan 02-05: group[N] inputy spoza formy (np. #group_5 "Tekstura materiału"
|
||
// w .product-bar-box). $form.serialize() ich nie łapie. Enumeruj i dołącz.
|
||
var externalGroups = '';
|
||
$('[name^="group["]').each(function() {
|
||
var $el = $(this);
|
||
// Skip te ktore juz sa w formie (bo $form.serialize() je ma)
|
||
if ($el.closest('#add-to-cart-or-refresh').length) return;
|
||
// Dla radio/checkbox: uwzglednij tylko checked
|
||
if (($el.attr('type') === 'radio' || $el.attr('type') === 'checkbox') && !$el.prop('checked')) return;
|
||
var n = $el.attr('name');
|
||
var v = $el.val();
|
||
if (v === undefined || v === null || v === '') return;
|
||
externalGroups += '&' + encodeURIComponent(n) + '=' + encodeURIComponent(v);
|
||
});
|
||
```
|
||
|
||
Potem zmień payload line na:
|
||
```js
|
||
var payload = $form.serialize() + '&qty=' + encodeURIComponent(qty) + externalGroups + '&' + sqFields + '&add=1&action=update';
|
||
```
|
||
|
||
Dopisz IDENTYCZNY blok w product.tpl inline mirror (używając `jQuery` zamiast `$`).
|
||
|
||
**Unikaj:**
|
||
- Przesuwania `<select id="group_5">` do formy — Bootstrap grid constraint (identyczna przyczyna jak w Plan 02-02 dla button+qty).
|
||
- Double-inclusion `group[N]` jesli jest zarazem w formie i outside (defensive `closest('#add-to-cart-or-refresh').length` check).
|
||
- Nadpisywania innych non-group pol spoza formy — filter strict `[name^="group["]`.
|
||
- Modyfikacji Plan 02-03 sq fields injection (activation independent).
|
||
</action>
|
||
<verify>
|
||
Node syntax: `node -c themes/ayon/assets/js/custom.js`.
|
||
</verify>
|
||
<done>AC-1 (payload zawiera group[5]) oraz AC-3 (group[4] nadal obecny) satisfied po Task 3 live verify.</done>
|
||
</task>
|
||
|
||
<task type="auto">
|
||
<name>Task 2: FTP deploy obu plików</name>
|
||
<files>themes/ayon/assets/js/custom.js, themes/ayon/templates/catalog/product.tpl</files>
|
||
<action>
|
||
```bash
|
||
curl -s -T "themes/ayon/assets/js/custom.js" "ftp://projectpro:i6B.b5P%7Bd6@newwalls.pl/public_html/themes/ayon/assets/js/custom.js" -w "js:%{http_code}\n"
|
||
curl -s -T "themes/ayon/templates/catalog/product.tpl" "ftp://projectpro:i6B.b5P%7Bd6@newwalls.pl/public_html/themes/ayon/templates/catalog/product.tpl" -w "tpl:%{http_code}\n"
|
||
```
|
||
|
||
Expected: `js:226` + `tpl:226`.
|
||
|
||
Post-deploy fetch + grep `externalGroups` na remote custom.js + product page HTML (confirm).
|
||
</action>
|
||
<verify>
|
||
Via Playwright `fetch('/themes/ayon/assets/js/custom.js', {cache:'no-store'})` zawiera `externalGroups` string.
|
||
</verify>
|
||
<done>Deploy confirmed, Task 3 gate available.</done>
|
||
</task>
|
||
|
||
<task type="checkpoint:human-verify" gate="blocking">
|
||
<name>Task 3: Live verification via Playwright — wszystkie 4 AC</name>
|
||
<what-built>
|
||
POST payload builder rozszerzony o external `[name^="group"]` inputs spoza formy. Efekt: wybrana struktura materiału (`group[5]`) trafia do koszyka.
|
||
</what-built>
|
||
<how-to-verify>
|
||
**Setup Playwright network capture:**
|
||
1. `browser_navigate https://newwalls.pl/pl/prestige/294-4181-rain-of-flowers.html?_new=1`
|
||
2. Wait 5s (interval initial render Plan 02-04 + bindings).
|
||
3. Change `#group_5` select value do non-default (eg. "10" lub "11"):
|
||
```js
|
||
const sel = document.getElementById('group_5');
|
||
sel.value = '10'; // lub inna available option
|
||
sel.dispatchEvent(new Event('change', { bubbles: true }));
|
||
```
|
||
4. Wymusic piece config: `document.getElementById('checkbox-piece').click()` (jesli nie checked).
|
||
5. Dispatchowac piece-width/height input eventy (50×50 baseline).
|
||
|
||
**AC-1: POST payload capture:**
|
||
6. `browser_network_requests` przed kliknieciem — baseline.
|
||
7. Klik `.add-to-cart` button.
|
||
8. Wait 2s na POST completion.
|
||
9. `browser_network_requests` — znajdz POST do `/pl/koszyk` (lub current URL), pobrac requestBody, grep `group[5]=10`.
|
||
**Expected:** payload contains `group%5B5%5D=10` (URL encoded).
|
||
|
||
**AC-2: Cart display:**
|
||
10. Po POST, sprawdz `.blockcart` lub navigate do `/pl/koszyk`, czytac produkty — wybrana struktura powinna byc visible w attribute combination.
|
||
|
||
**AC-3: Default case:**
|
||
11. Reload, zostaw defaults (`group[5]=9`, `group[4]=5`).
|
||
12. Add to cart, sprawdz payload zawiera `group%5B4%5D=5` AND `group%5B5%5D=9`.
|
||
|
||
**AC-4: OLD layout regression:**
|
||
13. Temporary flip IP gate `'89.69.31.86'` → `'255.255.255.255'` w product.tpl via Edit + FTP.
|
||
14. Navigate, add to cart → PS core flow, zero JS errors, cart updates.
|
||
15. RESTORE IP gate.
|
||
|
||
Jesli ktores AC nie spelnione — opisz specific failure (payload field missing, cart wrong structure, etc.).
|
||
</how-to-verify>
|
||
<resume-signal>Type "approved" LUB describe AC failure.</resume-signal>
|
||
</task>
|
||
|
||
</tasks>
|
||
|
||
<boundaries>
|
||
|
||
## DO NOT CHANGE
|
||
- Plan 02-03 add-to-cart handler core logic (piece validation, sync is_crop, sqFields build, modal fetch) — rozszerzamy tylko payload line.
|
||
- Markup template product.tpl — nie przesuwamy `<select id="group_5">` do formy (Bootstrap grid risk, identyczny powod jak przy button+qty w Plan 02-02).
|
||
- Shared `_partials/*.tpl` — tylko custom.js + inline mirror w product.tpl.
|
||
- `modules/squaremeter/*` — zero edit.
|
||
- OLD layout `{if REMOTE_ADDR != '89.69.31.86'}` block — niedotknięty (AC-4).
|
||
- Plan 02-04 price label logic — niezwiązany z tym fixem.
|
||
|
||
## SCOPE LIMITS
|
||
- Ten plan NIE rebuilduje attribute selector UI — tylko POST payload coverage.
|
||
- Ten plan NIE obsluguje edge case'ow (np. disabled options, `group[N]` w modal/hidden tab) poza tym ze filtruje radio/checkbox przez `:checked`.
|
||
- Ten plan NIE adresuje potencjalnego refresh variant flow gdy user zmienia `group[5]` (Plan 01-01 handler on `group[4]` change może lub może nie pokrywać `group[5]` — audit defer).
|
||
- Brak nowych dependencies.
|
||
|
||
</boundaries>
|
||
|
||
<verification>
|
||
Przed UNIFY:
|
||
- [ ] AC-1: Playwright captures POST body zawierający `group%5B5%5D=<selected>`.
|
||
- [ ] AC-2: Cart pokazuje wybraną strukturę.
|
||
- [ ] AC-3: Default case — `group[4]=5` AND `group[5]=9` oba w payload.
|
||
- [ ] AC-4: OLD layout test pass, zero JS errors.
|
||
- [ ] Git diff clean: zero zmian poza custom.js + product.tpl.
|
||
- [ ] Kod identyczny w obu miejscach.
|
||
- [ ] FTP deploy 226/226.
|
||
</verification>
|
||
|
||
<success_criteria>
|
||
- Wszystkie 4 AC pass w live Playwright test.
|
||
- Zero regresji: kolor variant, customization, modal, price label (Plan 01-01/02-02/02-03/02-04) nietknięte.
|
||
- SUMMARY.md dokumentuje pattern "enumerate external group inputs for PS attribute groups poza formą".
|
||
</success_criteria>
|
||
|
||
<output>
|
||
Po zakończeniu: `.paul/phases/02-product-actions-fixes/02-05-SUMMARY.md`
|
||
</output>
|