Plan 02-01 (piece/crop configurator, complete):
- #piece reuse z shared partial product-cover-thumbnails.tpl
- 8 hidden inputs (is_crop, crop_pos_x/y, crop_width/height, piece_bg_top/left, is_reflection) w formie #add-to-cart-or-refresh
- Defensive setup w custom.js: setTimeout(600) init, no-op override totalpriceinfospecific/prod, DOM stubs
- CSS scope pod body#product .product-size-data .product-size-data--new
Plan 02-02 (add-to-cart submission, PARTIAL):
- Capture-phase native addEventListener (useCapture=true) blokuje PS core crash
(button poza formą w nowym layoucie — closest('form') zwracało 0)
- Manualny AJAX POST: form.serialize() + qty + add=1&action=update do /pl/koszyk
- Fancybox-blocker port z custom.js:327 (nie odpalał się bo selector 0 matches)
- Manual sync is_crop/crop_width/height przed POST (obejście crash checkedHandler)
- prestashop.emit('updatedCart') + defensive blockcart refresh fetch
- Loading spinner + success flash CSS
- Inline handler mirror w product.tpl z idempotency guard (window.__p02p02Bound)
— cache-buster dla browser cachowanego custom.js
Deferred do Plan 02-03 (customization + modal blocker dla production):
- Customization nie zapisuje się (squaremeter hook gate'owany discretion=on + brak dimension fields)
- Success modal (wymaga POST do /module/ps_shoppingcart/ajax)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, delegation
| phase | plan | type | wave | depends_on | files_modified | autonomous | delegation | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-product-actions-fixes | 03 | execute | 1 |
|
|
false | off |
Purpose
Plan 02-02 uruchomił POST, ale bez tych dwóch punktów nowy layout jest nieakceptowalny dla klienta końcowego: nie widzi potwierdzenia akcji (modal) ani co zamówił (customization details w cart). Jest to blocker dla usunięcia IP gate (REMOTE_ADDR == '89.69.31.86') i publikacji layout'u szerszemu audytorium.
Output
- Wszystkie wymagane squaremeter hidden inputs w POST payload (
discretion=on,dim,qty,qty_alth,product_total_price_calc,id_product_attribute, +opcjonalne) obliczone z piece state + product data. - Po udanym cart update → drugi POST do
/module/ps_shoppingcart/ajax?action=add-to-cart→ render modal z response.modal HTML w fancybox (lub native PS modal container). - Koszyk po reload pokazuje "Szczegóły" button przy produkcie, klik rozwija customization data (wymiary, is_crop, is_reflection).
- Plan 02-01 override
totalpriceinfospecificPOZOSTAJE (nie ruszamy — bypass, nie restore). Cena kalkulacja może być nieprecyzyjna w tym planie (defer do Plan 02-04). - Summary:
.paul/phases/02-product-actions-fixes/02-03-SUMMARY.md.
Prior Work
@.paul/phases/02-product-actions-fixes/02-01-SUMMARY.md @.paul/phases/02-product-actions-fixes/02-02-SUMMARY.md
Source Files
@themes/ayon/templates/catalog/product.tpl @themes/ayon/assets/js/custom.js @modules/squaremeter/squaremeter.php
Line 843+: hookActionObjectCartUpdateBefore — gate'owany discretion=on, czyta:
dim, qty, qty_alth, qty_alt, qty_altd, extrafeevalue, wastevalue,
product_total_price_calc, calculated_total, grand_calculated_total,
converted_ea, directinput
@modules/squaremeter/override/classes/Cart.php
_addCustomization2(): INSERT INTO customization + customized_data
@modules/squaremeter/override/modules/ps_shoppingcart/controllers/front/ajax.php
Endpoint dla action=add-to-cart → renderModal() zwraca modal HTML
@modules/squaremeter/views/templates/front/header_surface.tpl
Stary flow squaremeter — inspiracja jakie fields + jak computed
## Required Skills| Skill | Priority | When to Invoke | Loaded? |
|---|---|---|---|
Playwright MCP (mcp__plugin_playwright_playwright__*) |
required | Task 1 (capture OLD layout POST), Task 3 regression | ○ |
context-mode (mcp__plugin_context-mode_context-mode__*) |
optional | Eksploracja squaremeter hooks + PS endpoints | ○ |
BLOCKING: Playwright MCP wymagany w Task 1 — musimy zobaczyć EXACT payload starego layoutu (fields + wartości) zanim zainżynierujemy mapowanie w nowym.
Skill Invocation Checklist
- Playwright MCP dostępny
- Dostęp do testowego środowiska gdzie można przełączyć layouty (IP switch lub URL param)
<acceptance_criteria>
AC-1: Customization zapisuje się podczas add-to-cart
Given user skonfigurował piece (width=200, height=150) w nowym layoucie
And formularz ma is_crop=1, crop_width=200, crop_height=150, piece_bg_top/left
When user klika "Dodaj do koszyka"
Then POST payload zawiera `discretion=on&dim=200x150&qty=200&qty_alth=150&product_total_price_calc=<value>&id_product_attribute=<value>`
And response HTTP 200 z `id_customization > 0`
And w DB tabela `customization` ma nowy wiersz z `id_cart=<active>&id_product=202&in_cart=1`
And w DB tabela `customized_data` ma wiersz z `index=<WD_CUSTOMIZATION_INDEX>&value='200x150'`
AC-2: Success modal pokazuje się po dodaniu
Given udany cart update z AC-1
When backend potwierdzi success
Then wychodzi drugi POST do `/module/ps_shoppingcart/ajax?action=add-to-cart` z `id_product`, `id_product_attribute`, `id_customization`
And response zawiera `modal: '<HTML>...'`
And modal renderuje się (w fancybox lub native container) z:
- Nazwą produktu
- Podziękowaniem / komunikatem success
- Dwoma przyciskami: "Kontynuuj zakupy" (zamyka modal) i "Przejdź do koszyka" (nawiguje do /pl/koszyk)
AC-3: Koszyk pokazuje "Szczegóły" button + customization data
Given product dodany przez nowy layout z customization
When user nawiguje do /pl/koszyk
Then cart item ma button/link "Szczegóły" (lub zwinięty blok z danymi)
And kliknięcie rozwija customization:
- Wymiary: "200 x 150 cm"
- Czy fragment: tak/nie (is_crop)
- Czy odbicie lustrzane: tak/nie (is_reflection)
AC-4: Brak double-add (handler idempotency po success)
Given user klika "Dodaj do koszyka" jedno klikniecie
When handler przetwarza
Then wychodzi dokładnie 1 POST do /koszyk + 1 POST do /module/ps_shoppingcart/ajax
And w koszyku jest 1 produkt (nie 2)
AC-5: Zero regresji starego layoutu
Given strona produktu poza IP 89.69.31.86
When user konfiguruje piece + dimensions + klika "Dodaj do koszyka"
Then flow identyczny jak przed Plan 02-03 (baseline network capture)
And customization save nadal działa (existing feature)
And modal nadal działa (existing feature)
</acceptance_criteria>
Task 1: Capture OLD layout POST payload (ground truth) Nothing coded yet — live data capture. Musimy zobaczyć DOKŁADNIE jakie fields stary layout wysyła przy add-to-cart, z jakimi wartościami, w jakiej kolejności. Bez tego ryzyko przegapienia wymaganego pola (np. `directinput`, `qty_alt`, `calculated_total`). Via Playwright MCP — OLD layout (IP != 89.69.31.86, np. przez URL param `?_test_old=1` jeśli jest albo tymczasowo komentować IP check w product.tpl dla tej sesji):1. Navigate: dowolny produkt na starym layoucie
2. Install diagnostic:
```js
window.__oldPayload = null;
const _ajax = jQuery.ajax;
jQuery.ajax = function(cfg) {
const url = (typeof cfg === 'string') ? cfg : cfg && cfg.url;
if (String(url).match(/cart|koszyk|ps_shoppingcart/) && cfg.type === 'POST') {
window.__oldPayload = { url, data: cfg.data, headers: cfg.headers };
}
return _ajax.apply(jQuery, arguments);
};
```
3. Sformułuj pełny user flow: wybierz wariant kolorystyczny + materiał + wprowadź wymiary (np. 200×150) + ustaw piece position + kliknij "Dodaj do koszyka"
4. Odczytaj `window.__oldPayload` + sprawdź Network tab dla wszystkich XHR/fetch do `cart` i `ps_shoppingcart`
5. Dokumentacja wszystkich parametrów:
- Pierwszy POST (cart update): wszystkie name=value pairs
- Drugi POST (modal fetch if exists): endpoint + payload
- Suffix pattern (`_<id_product>` lub brak)
- Format `dim` (np. "200x150" vs "200X150" vs "200 x 150")
- Czy `product_total_price_calc` jest integer czy decimal
6. Zapisać w komentarzu checkpoint'a listę fields z przykładowymi wartościami
Resume-signal: wklej dokładne pole-wartość z OLD POST + zatwierdź że to jest wzór
który naśladujemy w Task 2.
Podaj fields z OLD POST payload (przykład: `discretion=on&dim=200x150&qty=200&qty_alth=150&product_total_price_calc=597.00&...`) LUB flag "use sensible defaults" jeśli nie masz dostępu do OLD layoutu na prod.
Task 2: Inject squaremeter fields do POST payload w nowym layoucie
themes/ayon/assets/js/custom.js, themes/ayon/templates/catalog/product.tpl
W OBU miejscach (handler w custom.js linie 993-1113 + inline mirror w product.tpl
linie 757+) zmodyfikuj payload construction:
Przed `$.ajax({ ... data: payload })`:
```js
// Phase 02 Plan 02-03: inject squaremeter fields for customization save
var pieceW = parseInt($('#piece-width').val(), 10) || 100;
var pieceH = parseInt($('#piece-height').val(), 10) || 100;
var idProductAttribute = parseInt($('input[name=id_product_attribute], #idCombination').val(), 10)
|| window.product_page_product_combination
|| 0;
// Price calc: use product base price × area (m²) as fallback.
// Exact price jest Plan 02-04 scope — tu chodzi tylko o niezero value żeby hook przeszedł.
var areaM2 = (pieceW / 100) * (pieceH / 100);
var basePrice = parseFloat($('#product_base_price, #product_fixed_price').val())
|| parseFloat($('.product-prices .current-price').first().text().replace(/[^\d.,]/g, '').replace(',', '.'))
|| 0;
var totalPriceCalc = (basePrice * areaM2).toFixed(2);
var sqFields = 'discretion=on'
+ '&dim=' + pieceW + 'x' + pieceH
+ '&qty=' + pieceW // squaremeter qty = width (NIE cart quantity!)
+ '&qty_alth=' + pieceH
+ '&product_total_price_calc=' + totalPriceCalc
+ '&id_product_attribute=' + idProductAttribute
+ '&calculated_total=' + areaM2.toFixed(4)
+ '&grand_calculated_total=' + areaM2.toFixed(4);
payload = sqFields + '&' + payload; // prepend, bo niektóre pola mogą się powtórzyć — ostatni wygrywa w PHP
```
**Adjust payload** żeby NASZ qty (cart quantity) nie był nadpisywany. PS uses `qty` jako
product quantity; squaremeter czyta `qty` jako dimension width. Konflikt!
Rozwiązanie: nie wysyłać cart `qty=1` bezpośrednio; zamiast tego dodać je jako
`quantity_wanted=1` lub upewnić się że nasz `qty` (dimension) jest the one in POST.
**Task 1 da odpowiedź co stary layout robi tutaj — może używa suffix** (`qty_<id_product>`)
dla dimension a `qty` dla cart quantity.
Avoid:
- Zmiana nazwy cart quantity field (PS core by tego nie zaakceptował)
- Hardcode basePrice (read dynamically)
- Zakładanie że `id_product_attribute` jest w formie (może być w innej formie lub
jako Smarty var — Task 1 pokaże)
Po Task 1 adjust field list do tego co stary POST zawiera — możliwe że są DODATKOWE
pola których tu nie wymieniliśmy (np. `directinput`, `converted_ea`).
Playwright live test nowego layoutu:
1. Navigate + piece config
2. Click add-to-cart
3. Network tab: sprawdź POST do /koszyk — payload zawiera `discretion=on&dim=200x150&qty=200&qty_alth=150&product_total_price_calc=&id_product_attribute=`
4. Response: `success:true, id_customization: `
5. DB check (via PS admin SQL): `SELECT * FROM ps_customization WHERE id_cart = ` → 1 wiersz, `in_cart=1` po finalizacji
AC-1 satisfied.
Task 3: Chain POST do ps_shoppingcart/ajax + render modal
themes/ayon/assets/js/custom.js, themes/ayon/templates/catalog/product.tpl
W success callback po udanym cart POST (w OBU miejscach: custom.js + inline product.tpl),
po `emit('updatedCart')`, przed lub zamiast blockcart refresh:
```js
// Phase 02 Plan 02-03: fetch success modal
if (resp && resp.success && resp.id_product) {
$.ajax({
url: '/module/ps_shoppingcart/ajax',
type: 'POST',
data: {
action: 'add-to-cart',
id_product: resp.id_product,
id_product_attribute: resp.id_product_attribute,
id_customization: resp.id_customization
},
dataType: 'json',
success: function(modalResp) {
if (modalResp && modalResp.modal) {
$.fancybox({
content: modalResp.modal,
minWidth: 400,
maxWidth: 800,
padding: 20,
autoSize: true
});
}
if (modalResp && modalResp.preview) {
$('.blockcart').replaceWith(modalResp.preview);
}
}
});
}
```
**Uwaga:** endpoint path `/module/ps_shoppingcart/ajax` może być w PL jako
`/module/ps_shoppingcart/ajax` (path nie tłumaczony) ale warto sprawdzić w Task 1
(zobaczyć w Network czy URL jest exactly ten).
Jeśli modal HTML używa custom CSS (PS blockcart modal), może być problem z
stylowaniem w fancybox. Alternatywa: append `modal` HTML do `<body>` + show jako
native PS modal (Bootstrap `.modal.show`). Task 1 pokaże jak stary layout renderuje.
Avoid:
- Wywołanie obu: modal fetch + blockcart refresh (redundantnie) — modal resp już ma `preview`
- Zakładanie `resp.id_product` — PS może używać `idProduct` (camelCase) w JSON response
Po modal render, zbędny staje się nasz custom `.added-flash` animation. Zostaw
jako fallback gdy modal się nie zarenderuje.
Playwright live test:
1. Piece config + click add-to-cart
2. Network: 2 POSTy — pierwszy do /koszyk, drugi do /module/ps_shoppingcart/ajax
3. DOM: modal z response.modal HTML pokazuje się jako fancybox lub native modal
4. Modal zawiera:
- Nazwę produktu
- Link "Kontynuuj zakupy" (zamyka modal)
- Link "Przejdź do koszyka" (wskazuje na /pl/koszyk)
5. Navigate to /pl/koszyk → cart pokazuje "Szczegóły" button → klik rozwija customization data
AC-2, AC-3, AC-4 satisfied.
DO NOT CHANGE
- Plan 02-01 override
totalpriceinfospecific/prodno-op — nadal potrzebne żeby piece popup nie crash'ował. NIE restaurować squaremeter flow tutaj (zbyt ryzykowne + duży scope). modules/squaremeter/*— nie modyfikować modułu, żaden override ani edit.themes/ayon/templates/catalog/_partials/*— shared partials, nie ruszać.- Stara gałąź
{if ... != '89.69.31.86'}wproduct.tpl— nietknięta. - Istniejący
$('#add-to-cart-or-refresh button').on('click')w custom.js:327 — zostaje (niedziała w nowym, ale aktywne w starym). - Istniejący capture-phase handler z Plan 02-02 — rozbudowa w miejscu, nie rewrite.
SCOPE LIMITS
- Ten plan NIE dodaje UI dla live price calculation — cena w modalu/cart może być pokazana jako calculated value (read-only) ale bez UI do zmiany dimensions po dodaniu do cart. Plan 02-04 scope.
- Ten plan NIE dodaje dimension UI w product page (dimension input niezależny od piece popup) — user podaje wymiar przez piece popup (obecny mechanizm).
- Ten plan NIE restauruje squaremeter JS flow (header_surface.tpl etc.) — używamy BYPASS podejścia (bezpośrednio injection squaremeter fields w POST).
- Ten plan NIE rozwiązuje systemowego cache-buster dla
custom.js— inline mirror z Plan 02-02 pozostaje. Plan 02-05 scope. - Brak nowych dependencies.
<success_criteria>
- Wszystkie 5 AC pass w live Playwright test.
- Zero regresji w starym layoucie.
- Zero modyfikacji w module squaremeter.
- Plan 02-01 override nadal aktywny (nie przywracamy squaremeter JS).
- SUMMARY.md dokumentuje co działa, co deferred (Plan 02-04 cena UI, Plan 02-05 cache-buster). </success_criteria>