Nowa metoda basketUpdateCustomFields() w ShopBasketController — AJAX endpoint z walidacją required fields, przeliczaniem product_code (MD5 hash) i merge duplikatów. UI: przycisk "Edytuj personalizację" + formularz inline + JS. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.8 KiB
8.8 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous
| phase | plan | type | wave | depends_on | files_modified | autonomous | ||||
|---|---|---|---|---|---|---|---|---|---|---|
| 10-basket-edit-custom-fields | 01 | execute | 1 |
|
true |
Purpose
Klient, który pomyli się przy wpisywaniu personalizacji (np. grawer, dedykacja), musi teraz usunąć produkt z koszyka i dodać go ponownie z poprawnymi danymi. To frustrujące UX — edycja inline jest naturalnym oczekiwaniem.
Output
- Przycisk "Edytuj" przy personalizacjach w koszyku
- Modal lub formularz inline do edycji wartości custom fields
- Endpoint AJAX do zapisania zmian w sesji koszyka
- Przeliczenie product_code (MD5 hash) po zmianie wartości
Source Files
@autoload/front/Controllers/ShopBasketController.php @templates/shop-basket/_partials/product-custom-fields.php @templates/shop-basket/basket-details.php @templates/shop-product/_partial/product-custom-fields.php @autoload/Domain/Product/ProductRepository.php @ajax.php
## Required Skills (from SPECIAL-FLOWS.md)No specialized flows configured — /frontend-design jest optional.
<acceptance_criteria>
AC-1: Przycisk edycji widoczny w koszyku
Given produkt w koszyku ma wypełnione custom fields (personalizacje)
When klient widzi szczegóły koszyka (basket-details)
Then przy każdej pozycji z custom fields widnieje przycisk "Edytuj personalizację"
And przycisk NIE pojawia się gdy produkt nie ma custom fields
AC-2: Formularz edycji wyświetla aktualne wartości
Given klient klika "Edytuj personalizację" przy pozycji koszyka
When otwiera się formularz edycji (modal lub inline)
Then formularz zawiera pola odpowiadające custom fields tego produktu
And pola są wypełnione aktualnymi wartościami z koszyka
And pola wymagane (is_required) są oznaczone jako wymagane
AC-3: Zapis zmian aktualizuje koszyk
Given klient zmienił wartości custom fields w formularzu edycji
When klika "Zapisz"
Then wartości custom fields w sesji koszyka są zaktualizowane
And product_code (MD5 hash) jest przeliczony z nowymi wartościami
And strona koszyka odświeża się pokazując nowe wartości
And ilość produktu i inne atrybuty nie ulegają zmianie
AC-4: Walidacja pól wymaganych
Given produkt ma custom field oznaczone jako is_required = 1
When klient próbuje zapisać formularz z pustym polem wymaganym
Then zapis jest blokowany
And wyświetlany jest komunikat o wymaganym polu
AC-5: Obsługa konfliktu duplikatu
Given koszyk zawiera dwa egzemplarze tego samego produktu z różnymi personalizacjami
When klient edytuje personalizację jednego tak, że staje się identyczna z drugim
Then pozycje zostają scalone (ilości zsumowane)
And w koszyku pozostaje jedna pozycja z łączną ilością
</acceptance_criteria>
Task 1: Endpoint AJAX do aktualizacji custom fields w koszyku autoload/front/Controllers/ShopBasketController.php, ajax.php Dodać metodę `basketUpdateCustomFields()` w ShopBasketController:1. Przyjmuje POST z parametrami:
- `product_code` — obecny klucz pozycji w koszyku (MD5 hash)
- `custom_field[ID]` — nowe wartości custom fields (tablica)
2. Logika metody:
- Pobierz pozycję koszyka po `product_code` z sesji
- Jeśli nie istnieje → zwróć błąd JSON
- Waliduj wymagane pola (pobierz metadane z ProductRepository::findCustomFieldCached)
- Jeśli walidacja nie przejdzie → zwróć błąd JSON z listą brakujących pól
- Zaktualizuj `custom_fields` w pozycji koszyka
- Przelicz nowy product_code: `md5(product_id . attributes . message . json_encode(new_custom_fields))`
- Jeśli nowy product_code == stary → tylko aktualizuj wartości
- Jeśli nowy product_code istnieje już w koszyku → scal pozycje (zsumuj ilość), usuń starą
- Jeśli nowy product_code nie istnieje → przenieś pozycję pod nowy klucz, usuń stary
- Przelicz sumę koszyka
- Zwróć JSON success
3. Zarejestruj endpoint w ajax.php pod kluczem `basket_update_custom_fields`
- Wzoruj się na istniejących endpointach koszyka (basket_add_product, basket_remove itp.)
Unikaj:
- NIE używaj match expressions (PHP < 8.0)
- NIE sklejaj SQL stringiem — custom fields są w sesji, nie w DB
- Escape wartości przy wyświetlaniu (htmlspecialchars), ale w sesji przechowuj surowe wartości
Testy PHPUnit przechodzą (php phpunit.phar).
Endpoint odpowiada na POST z prawidłowym JSON response.
AC-3, AC-4, AC-5 satisfied: endpoint aktualizuje custom fields, waliduje required, obsługuje merge duplikatów
Task 2: UI edycji personalizacji w szablonie koszyka
templates/shop-basket/_partials/product-custom-fields.php, templates/shop-basket/basket-details.php
1. W `product-custom-fields.php` dodać przycisk "Edytuj personalizację":
- Przycisk widoczny tylko gdy `$this->custom_fields` nie jest puste
- Atrybut data-product-code z kluczem pozycji koszyka
- Klasa CSS do stylowania (np. `btn-edit-custom-fields`)
2. Dodać ukryty formularz edycji (modal inline) pod przyciskiem:
- Dla każdego custom field: input z aktualną wartością
- Pola wymagane oznaczone `required` + wizualnie (gwiazdka)
- Typ pola (text/image) z metadanych custom field
- Przyciski "Zapisz" i "Anuluj"
- Formularz domyślnie ukryty (`display: none`)
3. W `basket-details.php` dodać JavaScript obsługujący:
- Klik "Edytuj" → pokaż formularz, ukryj wyświetlane wartości
- Klik "Anuluj" → ukryj formularz, pokaż wartości
- Klik "Zapisz" → AJAX POST do `basket_update_custom_fields`
- Walidacja client-side required fields przed wysłaniem
- Po sukcesie → przeładuj stronę koszyka (location.reload)
- Po błędzie → pokaż komunikat
4. Przekazać `product_code` do szablonu custom fields:
- W `basket-details.php` przy wywołaniu `Tpl::view('shop-basket/_partials/product-custom-fields', ...)`
dodać parametr `product_code` z kluczem pozycji
Unikaj:
- NIE dodawaj zewnętrznych bibliotek JS/CSS
- NIE zmieniaj struktury HTML istniejących elementów (dodawaj nowe)
- Escape wszystkich wartości w atrybutach HTML (htmlspecialchars)
- NIE używaj str_contains/str_starts_with (PHP 8.0+)
Wizualna weryfikacja: przycisk "Edytuj" widoczny w koszyku przy produktach z personalizacją.
Klik otwiera formularz z aktualnymi wartościami.
Zapis odświeża koszyk z nowymi wartościami.
AC-1, AC-2 satisfied: przycisk edycji widoczny, formularz wyświetla aktualne wartości
DO NOT CHANGE
- autoload/Domain/Product/ProductRepository.php (nie modyfikuj metod findCustomFieldCached, saveCustomFields)
- autoload/Domain/Order/OrderRepository.php (nie zmieniaj createFromBasket)
- templates/shop-product/ (szablony strony produktu bez zmian)
- autoload/Domain/Basket/BasketRepository.php (jeśli istnieje — nie modyfikuj)
SCOPE LIMITS
- Tylko koszyk (basket-details) — NIE podsumowanie zamówienia (summary-view)
- Tylko edycja wartości — NIE dodawanie/usuwanie pól custom fields
- Tylko pola typu text i image — nie dodawaj nowych typów pól
- NIE zmieniaj sposobu przechowywania custom fields w zamówieniach (pp_shop_order_products)
- NIE dodawaj testów PHPUnit dla warstwy widoku (templates) — testuj tylko logikę kontrolera
<success_criteria>
- Wszystkie testy PHPUnit przechodzą
- AC-1 do AC-5 spełnione
- Kod zgodny z PHP < 8.0
- XSS protection (htmlspecialchars) na wszystkich outputach
- Brak nowych zależności zewnętrznych </success_criteria>