---
phase: 10-basket-edit-custom-fields
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - autoload/front/Controllers/ShopBasketController.php
  - templates/shop-basket/_partials/product-custom-fields.php
  - templates/shop-basket/basket-details.php
  - ajax.php
autonomous: true
---

<objective>
## Goal
Dodać możliwość edycji personalizacji (custom fields) produktu bezpośrednio w koszyku, bez konieczności usuwania produktu i dodawania go od nowa.

## 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
</objective>

<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md

## 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
</context>

<skills>
## Required Skills (from SPECIAL-FLOWS.md)

No specialized flows configured — /frontend-design jest optional.

</skills>

<acceptance_criteria>

## AC-1: Przycisk edycji widoczny w koszyku
```gherkin
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
```gherkin
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
```gherkin
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
```gherkin
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
```gherkin
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>

<tasks>

<task type="auto">
  <name>Task 1: Endpoint AJAX do aktualizacji custom fields w koszyku</name>
  <files>autoload/front/Controllers/ShopBasketController.php, ajax.php</files>
  <action>
    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
  </action>
  <verify>
    Testy PHPUnit przechodzą (php phpunit.phar).
    Endpoint odpowiada na POST z prawidłowym JSON response.
  </verify>
  <done>AC-3, AC-4, AC-5 satisfied: endpoint aktualizuje custom fields, waliduje required, obsługuje merge duplikatów</done>
</task>

<task type="auto">
  <name>Task 2: UI edycji personalizacji w szablonie koszyka</name>
  <files>templates/shop-basket/_partials/product-custom-fields.php, templates/shop-basket/basket-details.php</files>
  <action>
    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+)
  </action>
  <verify>
    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.
  </verify>
  <done>AC-1, AC-2 satisfied: przycisk edycji widoczny, formularz wyświetla aktualne wartości</done>
</task>

</tasks>

<boundaries>

## 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

</boundaries>

<verification>
Before declaring plan complete:
- [ ] `php phpunit.phar` — wszystkie testy przechodzą (820+)
- [ ] Endpoint `basket_update_custom_fields` zwraca poprawny JSON
- [ ] Przycisk "Edytuj" widoczny w koszyku przy produktach z personalizacją
- [ ] Formularz edycji wyświetla aktualne wartości
- [ ] Zapis zmienia wartości w sesji i odświeża koszyk
- [ ] Pola required są walidowane (client + server side)
- [ ] Merge duplikatów działa poprawnie
- [ ] Brak regresji — istniejąca funkcjonalność koszyka działa bez zmian
</verification>

<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>

<output>
After completion, create `.paul/phases/10-basket-edit-custom-fields/10-01-SUMMARY.md`
</output>
