feat(13-protection-packages): Pakiety ochronne SOFT/PREMIUM z panelu WP
- Panel admina (wp-admin > Rezerwacje > Pakiety ochronne) do zarzadzania nazwami, cenami za dobe, aktywnoscia i opisami pakietow SOFT i PREMIUM (zapis w wp_options carei_protection_packages) - REST endpoint GET /carei/v1/protection-packages zwracajacy aktywne pakiety - Radio cards SOFT/PREMIUM w modalu rezerwacji nad pozycjami "Pakiety ochronne" z API (osobne zrodlo danych, separator wizualny) - Radio z deselect (klik zaznaczonego odznacza), natywny input z accent-color - Pakiet NIE wysylany w priceItems Softra (powodowalo HTTP 400) - zamiast tego doklejany do comments booking i zapisywany w _carei_protection_package meta - Summary frontend dokorysowuje wiersz pakietu w tabeli cen i dolicza do total gross (grandGross = softraGross + protectionTotal) - Plan 13-01 oznaczony jako superseded (klient zmienil zrodlo danych) - Phase 13 Complete, Milestone v0.5 Complete Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
21
.paul/phases/13-protection-packages/13-01-SUMMARY.md
Normal file
21
.paul/phases/13-protection-packages/13-01-SUMMARY.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
phase: 13-protection-packages
|
||||
plan: 01
|
||||
status: superseded
|
||||
superseded_by: 13-02-PLAN.md
|
||||
completed: 2026-04-20
|
||||
---
|
||||
|
||||
# Phase 13 Plan 01: Superseded
|
||||
|
||||
Plan 13-01 (pricing progowy min/doba/max z Softra API) **nie został wdrożony** — klient po audycie wybrał prostsze źródło danych: stała cena/dobę zarządzana w panelu WP.
|
||||
|
||||
**Supersession decision (2026-04-20):**
|
||||
- Plan 13-01 zakładał 3 progi cenowe pobierane z `additionalItems` API Softra
|
||||
- Klient potwierdził, że ceny pakietów ochronnych mają być zarządzane w WP (nie wyeksponowane w Softra)
|
||||
- Uproszczenie: jedna cena/dobę × liczba dób
|
||||
|
||||
**Plan wdrażający wymagania klienta:** [13-02-PLAN.md](./13-02-PLAN.md) — ukończony, SUMMARY: [13-02-SUMMARY.md](./13-02-SUMMARY.md).
|
||||
|
||||
---
|
||||
*Phase: 13-protection-packages, Plan: 01 (superseded)*
|
||||
317
.paul/phases/13-protection-packages/13-02-PLAN.md
Normal file
317
.paul/phases/13-protection-packages/13-02-PLAN.md
Normal file
@@ -0,0 +1,317 @@
|
||||
---
|
||||
phase: 13-protection-packages
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- wp-content/plugins/carei-reservation/includes/class-admin-panel.php
|
||||
- wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
|
||||
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
|
||||
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
- wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
|
||||
autonomous: false
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Dodać dwa stałe pakiety ochronne (SOFT, PREMIUM) w sekcji "Pakiety ochronne" modalu rezerwacji, z ceną za dobę zarządzaną w panelu administratora WordPress. Kalkulacja dzienna (price × liczba_dób). Wizualne oddzielenie od pozostałych pozycji API w tej sekcji.
|
||||
|
||||
## Purpose
|
||||
Klient potwierdził, że źródłem danych cenowych dla pakietów ochronnych jest panel WP (nie API Softra). Stary plan 13-01 (pricing progowy min/doba/max z Softra) został odrzucony. Nowe wymaganie upraszcza model: stała cena/dobę × liczba dób rezerwacji, zarządzana przez admina w WP.
|
||||
|
||||
## Output
|
||||
- Nowa podstrona w menu "Rezerwacje" → "Pakiety ochronne" (name, price/doba, aktywne, opis dla SOFT i PREMIUM)
|
||||
- Endpoint `GET /carei/v1/protection-packages` dla frontendu
|
||||
- Dwa kafelki w modalu nad/pod istniejącymi opcjami "Pakiety ochronne" z delikatnym separatorem
|
||||
- Cena wyświetlana: "X zł/doba" + wyliczony total dla wybranych dat
|
||||
- Wybrany pakiet uwzględniony w `priceItems` booking submission i zapisany w CPT
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Source Files
|
||||
@wp-content/plugins/carei-reservation/includes/class-admin-panel.php
|
||||
@wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
|
||||
@wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
|
||||
@wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
@wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
|
||||
|
||||
## Prior Work
|
||||
Plan 13-01 (BLOCKED) zakładał pricing progowy z pola `additionalItems` Softra API — **odrzucony**. Ten plan (13-02) zastępuje 13-01. Zbiór SCOPE LIMITS odwraca się: backend+admin SĄ w zakresie, pricing tierowy poza zakresem.
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Panel administratora — konfiguracja pakietów
|
||||
```gherkin
|
||||
Given admin otwiera menu "Rezerwacje" w WP
|
||||
When klika podstronę "Pakiety ochronne"
|
||||
Then widzi formularz z dwoma sekcjami (SOFT i PREMIUM) zawierającymi:
|
||||
- nazwa wyświetlana (text, default "Ubezpieczenie SOFT" / "Ubezpieczenie PREMIUM")
|
||||
- cena za dobę (number, min 0, step 0.01)
|
||||
- aktywne (checkbox, default on)
|
||||
- opis/zakres usług (textarea, opcjonalny)
|
||||
And zapis formularza trwale przechowuje wartości w wp_options (carei_protection_packages)
|
||||
```
|
||||
|
||||
## AC-2: Endpoint REST z danymi pakietów
|
||||
```gherkin
|
||||
Given w panelu admina są zapisane pakiety
|
||||
When frontend wywołuje GET /wp-json/carei/v1/protection-packages
|
||||
Then endpoint zwraca JSON { soft: {name, pricePerDay, active, description}, premium: {...} }
|
||||
And zwraca tylko pakiety z active=true (nieaktywne pomijane lub flagowane jako inactive)
|
||||
```
|
||||
|
||||
## AC-3: Render kafelków w modalu z separatorem
|
||||
```gherkin
|
||||
Given otwarto modal rezerwacji, wybrano segment + oddział + daty
|
||||
When sekcja "Pakiety ochronne" renderuje się
|
||||
Then nad listą opcji API pojawiają się 2 kafelki SOFT + PREMIUM (tylko te z active=true)
|
||||
And kafelki są oddzielone delikatną linią/marginesem od pozostałych pozycji "Pakiety ochronne" pochodzących z pricelist API
|
||||
And każdy kafelek pokazuje: nazwę, cenę "X zł/doba", wyliczony total "= Y zł" dla aktualnej liczby dób, opis (jeśli ustawiony)
|
||||
```
|
||||
|
||||
## AC-4: Wybór i dynamiczne przeliczanie
|
||||
```gherkin
|
||||
Given użytkownik widzi oba kafelki pakietów
|
||||
When klika SOFT
|
||||
Then SOFT jest zaznaczony (radio-style), PREMIUM odznaczony
|
||||
When następnie klika PREMIUM
|
||||
Then SOFT jest odznaczony, PREMIUM zaznaczony
|
||||
When klika ponownie zaznaczony pakiet
|
||||
Then zostaje odznaczony (dozwolony brak wybranego pakietu)
|
||||
When zmienia daty rezerwacji
|
||||
Then wyliczony total (price × days) aktualizuje się automatycznie na obu kafelkach
|
||||
```
|
||||
|
||||
## AC-5: Pakiet w booking submission i CPT
|
||||
```gherkin
|
||||
Given użytkownik wybrał pakiet SOFT
|
||||
When klika "Pokaż podsumowanie" → "Zarezerwuj"
|
||||
Then wybrany pakiet jest dołączony do priceItems w zapytaniu do Softra (price, priceBeforeDiscount, name, quantity=days)
|
||||
And po zapisie rezerwacji widoczny w CPT carei_reservation w polu "extras" lub dedykowanym polu meta
|
||||
And w summary overlay pojawia się linia "Ubezpieczenie SOFT — Y zł"
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Backend — admin settings page + REST endpoint</name>
|
||||
<files>wp-content/plugins/carei-reservation/includes/class-admin-panel.php, wp-content/plugins/carei-reservation/includes/class-rest-proxy.php</files>
|
||||
<action>
|
||||
A. W `class-admin-panel.php`:
|
||||
- Dodać metodę `register_protection_packages_page()` hookowaną przez `admin_menu`: `add_submenu_page('edit.php?post_type=carei_reservation', 'Pakiety ochronne', 'Pakiety ochronne', 'manage_options', 'carei-protection-packages', [$this, 'render_protection_packages_page'])`
|
||||
- Dodać `render_protection_packages_page()`: formularz POST z nonce `carei_protection_packages`, dwie sekcje (SOFT, PREMIUM) z polami: `name`, `pricePerDay`, `active`, `description`.
|
||||
- Dodać obsługę POST: walidacja nonce + `manage_options`, sanityzacja (`sanitize_text_field` dla name, `floatval` + clamp >=0 dla price, `boolean` checkbox, `sanitize_textarea_field`), zapis do `update_option('carei_protection_packages', $data)`.
|
||||
- Defaulty (przy pierwszym wczytaniu / pustym option):
|
||||
`soft = { name: 'Ubezpieczenie SOFT', pricePerDay: 0, active: true, description: '' }`
|
||||
`premium = { name: 'Ubezpieczenie PREMIUM', pricePerDay: 0, active: true, description: '' }`
|
||||
- Dodać helper statyczny `public static function get_protection_packages()` zwracający aktualną tablicę (merge z defaultami).
|
||||
- Dodatkowo dopisać obsługę zapisu wybranego pakietu do post_meta w `save_reservation()` (jeśli `$data['protectionPackage']` podane — zapisać jako `_carei_protection_package` JSON: `{key, name, pricePerDay, days, total}`) oraz wyświetlić w meta boxie pod sekcją extras.
|
||||
|
||||
B. W `class-rest-proxy.php`:
|
||||
- Zarejestrować nowy route w `register_routes()`:
|
||||
```php
|
||||
register_rest_route( self::NAMESPACE, '/protection-packages', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_protection_packages' ),
|
||||
'permission_callback' => '__return_true',
|
||||
) );
|
||||
```
|
||||
- Dodać metodę `get_protection_packages()`: odczyt `Carei_Admin_Panel::get_protection_packages()`, filtrowanie tylko `active === true`, zwrot jako `rest_ensure_response({ soft: {...}|null, premium: {...}|null })`.
|
||||
|
||||
Avoid: Nie tworzyć custom table — wystarczy wp_options (spójne z MVP). Nie dodawać JS-a do panelu admina (formularz statyczny, native WP). Nie przenosić `save_reservation()` logic — tylko dopisać pole.
|
||||
</action>
|
||||
<verify>
|
||||
- `wp-admin/edit.php?post_type=carei_reservation&page=carei-protection-packages` — strona renderuje formularz
|
||||
- Zmiana ceny + zapis → komunikat "Zapisano" i wartości persystują po reload
|
||||
- `curl https://carei.pagedev.pl/wp-json/carei/v1/protection-packages` → JSON z oboma pakietami
|
||||
- Ustawienie SOFT inactive → endpoint zwraca `soft: null` (lub bez klucza)
|
||||
</verify>
|
||||
<done>AC-1, AC-2 satisfied: Admin może edytować pakiety, endpoint REST eksponuje dane.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Frontend HTML — kontener kafelków pakietów z separatorem</name>
|
||||
<files>wp-content/plugins/carei-reservation/includes/class-elementor-widget.php, wp-content/plugins/carei-reservation/assets/css/carei-reservation.css</files>
|
||||
<action>
|
||||
A. W `class-elementor-widget.php`, w sekcji `<div class="carei-form__divider"><span>Pakiety ochronne</span></div>`:
|
||||
- BEZPOŚREDNIO nad istniejącym `<div class="carei-form__row" id="carei-insurance-container">` dodać:
|
||||
```html
|
||||
<div class="carei-form__row carei-form__row--protection-packages" id="carei-protection-packages-container">
|
||||
<!-- Dynamicznie wstrzykiwane: 2 kafelki SOFT/PREMIUM z panelu WP -->
|
||||
</div>
|
||||
<div class="carei-form__protection-divider" aria-hidden="true"></div>
|
||||
```
|
||||
- Pozostawić istniejący `#carei-insurance-container` bez zmian (nadal renderuje pozycje z API Softra, jeśli są).
|
||||
|
||||
B. W `carei-reservation.css`:
|
||||
- `.carei-form__row--protection-packages` — grid/flex z 2 kolumnami (desktop), wrap na mobile.
|
||||
- `.carei-form__protection-package` — karta: padding, border 2px solid transparent, border-radius 12px, background #f8f8fb, cursor pointer, tranzycja.
|
||||
- `.carei-form__protection-package.is-selected` — border #2F2482, background #fff z subtelnym shadow.
|
||||
- `.carei-form__protection-package__name` — bold, color #2F2482.
|
||||
- `.carei-form__protection-package__price` — większy font, strong.
|
||||
- `.carei-form__protection-package__unit` — mały font szary "/doba".
|
||||
- `.carei-form__protection-package__total` — color #FF0000 dla total.
|
||||
- `.carei-form__protection-package__desc` — mały font, szary.
|
||||
- `.carei-form__protection-divider` — margin-top/bottom, border-bottom 1px dashed rgba(47,36,130,0.15), height 0. Stanowi delikatny wizualny separator między pakietami WP a opcjami API.
|
||||
- Responsive: `@media (max-width: 640px)` → kolumna, pełna szerokość.
|
||||
|
||||
Avoid: Nie ruszać istniejącego `#carei-insurance-container` ani `.carei-form__extra-card` (służą pozostałym pozycjom API i extras).
|
||||
</action>
|
||||
<verify>
|
||||
- W edytorze Elementor / na stronie: DOM zawiera nowy `#carei-protection-packages-container` nad `#carei-insurance-container` wewnątrz sekcji "Pakiety ochronne"
|
||||
- Separator `.carei-form__protection-divider` widoczny w DOM
|
||||
- Style zaczytywane — brak błędów w DevTools
|
||||
</verify>
|
||||
<done>AC-3 satisfied (HTML skeleton + separator) — JS render w Task 3</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Frontend JS — render, radio behavior, dynamic recalculation, submission</name>
|
||||
<files>wp-content/plugins/carei-reservation/assets/js/carei-reservation.js</files>
|
||||
<action>
|
||||
1. **Wczytanie pakietów (przy init lub przy otwarciu modala):**
|
||||
- Dodać stan modułu: `var protectionPackages = { soft: null, premium: null };` oraz `var selectedProtectionKey = null;`.
|
||||
- Dodać referencję: `protectionContainer = document.getElementById('carei-protection-packages-container');` w sekcji `cacheDom()` (~linie 100-120).
|
||||
- Dodać funkcję `loadProtectionPackages()` wywołującą `fetch('/wp-json/carei/v1/protection-packages')` (REST nie wymaga nonce dla GET). Po otrzymaniu: zapis do `protectionPackages`, wywołanie `renderProtectionPackages()`.
|
||||
- Wywołać `loadProtectionPackages()` jednorazowo (np. w `init()` obok innych API calls, niezależnie od pricelist).
|
||||
|
||||
2. **Render `renderProtectionPackages()`:**
|
||||
- Jeśli kontener nie istnieje — return.
|
||||
- Wyczyścić container.
|
||||
- Dla każdego klucza `['soft','premium']`, jeśli pakiet istnieje i `active`:
|
||||
- Zbudować kafelek zgodnie z markup CSS z Task 2:
|
||||
```
|
||||
<label class="carei-form__protection-package" data-key="soft" data-price="X">
|
||||
<input type="radio" name="protectionPackage" value="soft" class="carei-form__protection-package__input" hidden>
|
||||
<span class="carei-form__protection-package__name">{name}</span>
|
||||
<span class="carei-form__protection-package__price">{pricePerDay} zł<span class="carei-form__protection-package__unit">/doba</span></span>
|
||||
<span class="carei-form__protection-package__total" data-role="total"></span>
|
||||
<span class="carei-form__protection-package__desc">{description}</span>
|
||||
</label>
|
||||
```
|
||||
- Podłączyć listener click do każdego kafelka: toggle `is-selected`, aktualizacja `selectedProtectionKey`, wywołanie `updateProtectionTotals()`.
|
||||
- Wywołać `updateProtectionTotals()` bezpośrednio po render.
|
||||
|
||||
3. **`updateProtectionTotals()`:**
|
||||
- Obliczyć `days` = liczba dób (wyznaczyć z dat `#carei-date-from` + `#carei-date-to` — użyć istniejącej funkcji/logiki; jeśli brak helper — obliczyć: `Math.max(1, Math.ceil((dateTo - dateFrom) / 86400000))`).
|
||||
- Dla każdego kafelka: aktualizacja `[data-role="total"]` → `= {price*days} zł`.
|
||||
- Jeśli daty nie są jeszcze ustawione (days === NaN) — total pusty.
|
||||
|
||||
4. **Hook w zmianę dat:**
|
||||
- Zlokalizować istniejący handler `change`/`input` na polach `#carei-date-from` i `#carei-date-to` (lub common `onDateChange()`) i dodać wywołanie `updateProtectionTotals()`. Alternatywnie dodać niezależne listenery.
|
||||
|
||||
5. **Radio deselect behavior:**
|
||||
- W click handlerze: jeśli `selectedProtectionKey === clickedKey` → odznacz (pusty stan, `selectedProtectionKey = null`). Inaczej ustaw jako wybrany, odznacz drugi kafelek.
|
||||
|
||||
6. **Submission + summary:**
|
||||
- W `getSelectedExtrasForApi()` (~linia 849): po zebraniu extras dodać warunkowo wybrany pakiet:
|
||||
```
|
||||
if (selectedProtectionKey && protectionPackages[selectedProtectionKey]) {
|
||||
var pkg = protectionPackages[selectedProtectionKey];
|
||||
var days = calculateDays();
|
||||
var total = pkg.pricePerDay * days;
|
||||
extrasArr.push({
|
||||
id: 'protection_' + selectedProtectionKey,
|
||||
name: pkg.name,
|
||||
price: total,
|
||||
priceBeforeDiscount: total,
|
||||
quantity: days,
|
||||
unit: 'doba'
|
||||
});
|
||||
}
|
||||
```
|
||||
- W payloadzie do `makeBooking`: dodać pole `protectionPackage: { key, name, pricePerDay, days, total }` (obok `priceItems`) — admin zapisze w meta.
|
||||
|
||||
Avoid: Nie modyfikować `buildExtraCard` ani logiki `extras[]` API Softra. Nie renderować pakietów jeśli `pricePerDay === 0` (opcjonalne — dopisać validation w admin, ale render sam skip tych z 0 jest też ok).
|
||||
</action>
|
||||
<verify>
|
||||
- Network tab: request do `/carei/v1/protection-packages` zwraca dane
|
||||
- Modal: w sekcji "Pakiety ochronne" widoczne 2 kafelki SOFT+PREMIUM nad separatorem
|
||||
- Kliknięcie SOFT: `is-selected`, kliknięcie PREMIUM: SOFT traci selekcję, PREMIUM dostaje
|
||||
- Kliknięcie zaznaczonego pakietu: odznaczenie
|
||||
- Zmiana dat 3→7 dni: total pakietu aktualizuje się (price × 7)
|
||||
- Wybór pakietu → podsumowanie → payload `priceItems` zawiera pakiet, `protectionPackage` przekazany do backendu
|
||||
- Zapisana rezerwacja w CPT zawiera wybrany pakiet w meta boxie
|
||||
</verify>
|
||||
<done>AC-3, AC-4, AC-5 satisfied: Kafelki działają w pełnym cyklu (render → wybór → kalkulacja → submission → zapis).</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>
|
||||
Panel admina "Pakiety ochronne" (wp-admin), endpoint REST, kafelki SOFT/PREMIUM w modalu z separatorem od opcji API, radio selection z deselect, dynamiczne przeliczanie price×days, pakiet w booking submission + zapis w CPT.
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
1. **Admin:** Zaloguj się do WP → menu "Rezerwacje" → "Pakiety ochronne".
|
||||
- Ustaw SOFT: nazwa "Ubezpieczenie SOFT", cena 25 zł/dobę, aktywne, opis krótki
|
||||
- Ustaw PREMIUM: nazwa "Ubezpieczenie PREMIUM", cena 50 zł/dobę, aktywne, opis krótki
|
||||
- Zapisz, przeładuj — wartości zostały
|
||||
2. **Frontend:** Otwórz carei.pagedev.pl → kliknij przycisk rezerwacji.
|
||||
- Wybierz segment, oddział, daty (3 dni)
|
||||
- Sekcja "Pakiety ochronne" — widzisz u góry 2 kafelki SOFT+PREMIUM, pod nimi linia separatora, następnie pozostałe opcje API (jeśli są)
|
||||
- SOFT pokazuje "25 zł/doba = 75 zł", PREMIUM "50 zł/doba = 150 zł"
|
||||
3. **Interakcja:** Kliknij SOFT → podświetlony. Kliknij PREMIUM → SOFT traci selekcję, PREMIUM zaznaczony. Kliknij PREMIUM ponownie → odznaczony.
|
||||
4. **Recalc:** Zmień daty na 7 dni → totale: SOFT 175 zł, PREMIUM 350 zł.
|
||||
5. **Submit:** Wybierz SOFT, dokończ flow → podsumowanie pokazuje "Ubezpieczenie SOFT — 175 zł" jako pozycję. Potwierdź rezerwację.
|
||||
6. **CPT:** wp-admin → Rezerwacje → otwórz nową pozycję → meta box zawiera informację o wybranym pakiecie.
|
||||
7. **Inactive test:** Ustaw SOFT active=false w adminie → modal pokazuje tylko PREMIUM.
|
||||
8. **Mobile (360×640):** Kafelki stackują się pionowo, czytelne, klikalne.
|
||||
</how-to-verify>
|
||||
<resume-signal>Napisz "approved" aby kontynuować do UNIFY, lub opisz problemy do naprawy.</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- `class-softra-api.php` — klient Softra API bez zmian
|
||||
- `buildExtraCard()` / `#carei-extras-container` / `input[name="extras[]"]` — logika regular extras pozostaje
|
||||
- `#carei-insurance-container` (istniejąca logika filtrowania pozycji ubezpieczeniowych z pricelist API) — pozostaje jako fallback dla pozycji z Softra, gdyby były
|
||||
- Sekcja "Wyjazd zagraniczny" i "Opcje dodatkowe"
|
||||
- Hero search form widget
|
||||
- Flow booking (customer/add → makebooking → confirm) poza dołączeniem pakietu do payload
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Brak pricing progowego (min/per-day/max) — ten pomysł z 13-01 jest odrzucony
|
||||
- Brak integracji z Softra API dla pakietów — dane tylko z panelu WP
|
||||
- Brak tłumaczeń multi-lang — hardcoded polski (spójne z resztą pluginu)
|
||||
- Brak custom DB table — wp_options wystarczy
|
||||
- Brak więcej niż 2 pakietów w UI adminu — tylko SOFT i PREMIUM (fixed)
|
||||
- Brak refaktoryzacji `getSelectedExtrasForApi()` — tylko dopisanie warunkowej gałęzi
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Panel admina "Pakiety ochronne" dostępny i zapisuje dane do wp_options
|
||||
- [ ] `GET /wp-json/carei/v1/protection-packages` zwraca poprawny JSON (z filtrem active)
|
||||
- [ ] Modal renderuje 2 kafelki nad istniejącym kontenerem insurance, z separatorem
|
||||
- [ ] Radio behavior: exclusive + deselect działają
|
||||
- [ ] Total przelicza się przy zmianie dat
|
||||
- [ ] Wybrany pakiet dołączany do priceItems w payloadzie booking
|
||||
- [ ] Pakiet widoczny w summary overlay i w CPT carei_reservation
|
||||
- [ ] Inactive pakiet ukryty w UI
|
||||
- [ ] Brak regresji w extras, abroad, reszcie formularza
|
||||
- [ ] Responsywność mobile (< 640px) działa
|
||||
- [ ] Wszystkie AC-1 – AC-5 spełnione
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Wszystkie taski zakończone
|
||||
- Wszystkie checki weryfikacji przechodzą
|
||||
- Brak błędów w konsoli przeglądarki i logach PHP
|
||||
- Human-verify checkpoint zatwierdzony
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/13-protection-packages/13-02-SUMMARY.md`
|
||||
</output>
|
||||
173
.paul/phases/13-protection-packages/13-02-SUMMARY.md
Normal file
173
.paul/phases/13-protection-packages/13-02-SUMMARY.md
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
phase: 13-protection-packages
|
||||
plan: 02
|
||||
subsystem: ui
|
||||
tags: [wordpress, rest-api, elementor, admin-panel, vanilla-js, insurance]
|
||||
|
||||
requires:
|
||||
- phase: 05-admin-panel
|
||||
provides: CPT carei_reservation, save_reservation() static, meta box infrastructure
|
||||
- phase: 03-booking-flow
|
||||
provides: booking payload pipeline, priceItems convention, summary overlay
|
||||
|
||||
provides:
|
||||
- Panel admina "Pakiety ochronne" (wp-admin → Rezerwacje → Pakiety ochronne)
|
||||
- REST endpoint GET /carei/v1/protection-packages (public)
|
||||
- Radio cards SOFT/PREMIUM w modalu z wizualnym separatorem od pozycji API
|
||||
- Integracja z summary overlay (details list + price table + totals)
|
||||
- Zapis wybranego pakietu w post_meta _carei_protection_package
|
||||
- Info o pakiecie doklejane do comments booking (rezerwacja Softra widzi pakiet w uwagach)
|
||||
|
||||
affects: [future-insurance-adjustments, pricing-customizations, admin-ux]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "WP options + REST proxy jako źródło danych niezależne od Softra"
|
||||
- "Hybrydowe podsumowanie: pozycje z Softra + lokalne dodatki łączone w summary overlay"
|
||||
- "Rozszerzenie comments Softra o strukturalne info pakietu (fallback dla braku dedicated field)"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- ".paul/phases/13-protection-packages/13-02-PLAN.md"
|
||||
- ".paul/phases/13-protection-packages/13-02-SUMMARY.md"
|
||||
modified:
|
||||
- "wp-content/plugins/carei-reservation/includes/class-admin-panel.php"
|
||||
- "wp-content/plugins/carei-reservation/includes/class-rest-proxy.php"
|
||||
- "wp-content/plugins/carei-reservation/includes/class-elementor-widget.php"
|
||||
- "wp-content/plugins/carei-reservation/assets/js/carei-reservation.js"
|
||||
- "wp-content/plugins/carei-reservation/assets/css/carei-reservation.css"
|
||||
|
||||
key-decisions:
|
||||
- "Dane pakietów w wp_options (opcja carei_protection_packages) — nie CPT ani custom table"
|
||||
- "Pakiet poza Softra priceItems — HTTP 400 przy próbie wysłania fałszywych ID"
|
||||
- "Pakiet w comments booking + osobny protectionPackage w payloadzie dla CPT"
|
||||
- "Summary frontend dokłada wiersz pakietu i oblicza grandTotal = softraGross + protectionTotal"
|
||||
- "Plan 13-01 superseded — pricing progowy odrzucony przez klienta na rzecz prostej ceny/dobę z WP"
|
||||
|
||||
patterns-established:
|
||||
- "REST route dla danych WP-managed: __return_true permission (publiczne read-only)"
|
||||
- "Filtrowanie active=true po stronie endpointu, frontend dostaje tylko aktywne"
|
||||
- "Radio z deselect przez JS click handler (natywne radio nie odznacza)"
|
||||
- "accent-color: var(--carei-blue) dla natywnych radio spójnych z design systemem"
|
||||
|
||||
duration: ~3h
|
||||
started: 2026-04-20T12:00:00Z
|
||||
completed: 2026-04-20T15:30:00Z
|
||||
---
|
||||
|
||||
# Phase 13 Plan 02: Pakiety ochronne SOFT+PREMIUM — Summary
|
||||
|
||||
**Dwa pakiety ochronne z ceną/doba zarządzane w panelu WP (wp_options), renderowane jako radio cards w modalu nad opcjami API, doliczane lokalnie w summary i przesyłane do Softra przez comments + lokalny meta CPT.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~3h |
|
||||
| Started | 2026-04-20T12:00:00Z |
|
||||
| Completed | 2026-04-20T15:30:00Z |
|
||||
| Tasks | 3 auto + 1 checkpoint (human-verify approved) |
|
||||
| Files modified | 5 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Panel administratora — konfiguracja pakietów | ✅ Pass | Submenu "Pakiety ochronne" w edit.php?post_type=carei_reservation, formularz z nazwą/ceną/aktywnym/opisem dla SOFT+PREMIUM, zapis w carei_protection_packages |
|
||||
| AC-2: Endpoint REST z danymi pakietów | ✅ Pass | GET /wp-json/carei/v1/protection-packages zwraca {soft, premium} z filtrem active=true |
|
||||
| AC-3: Render kafelków z separatorem | ✅ Pass | #carei-protection-packages-container nad #carei-insurance-container, .carei-form__protection-divider oddziela wizualnie |
|
||||
| AC-4: Wybór i dynamiczne przeliczanie | ✅ Pass (zmieniono UX) | Radio z deselect działa; sumowanie × days wyłączone w kafelku (decyzja UX z klientem — tylko "X zł/doba" szare po prawej, total dopiero w summary) |
|
||||
| AC-5: Pakiet w booking submission i CPT | ✅ Pass (rozwiązanie hybrydowe) | Pakiet w comments booking + protectionPackage w payloadzie + _carei_protection_package meta CPT + wiersz w summary overlay (details list + price table + totals) |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- **Zarządzanie niezależne od Softra** — admin może zmieniać ceny pakietów bez dotykania API wynajmowanego przez zewnętrznego dostawcę
|
||||
- **Rozwiązanie konfliktu API** — po pierwszej próbie wysłania pakietów jako `priceItems` Softra zwracała HTTP 400; refactor w locie: pakiet wyłączony z priceItems, doklejony do comments + zobaczony w summary po stronie frontu
|
||||
- **Spójność wizualna** — kafelki pakietów identyczne stylem z `.carei-form__extra-card` (nazwa blue, cena szara po prawej, bold, natywny radio z accent-color)
|
||||
- **Pełny cykl życia rezerwacji** — pakiet od momentu wyboru (radio) → comments Softra → CPT meta → meta box admina → wyświetlenie w formacie "nazwa — cena/doba × dni = total"
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `.paul/phases/13-protection-packages/13-02-PLAN.md` | Created | Plan zastępujący 13-01 (nowe założenia klienta) |
|
||||
| `.paul/phases/13-protection-packages/13-02-SUMMARY.md` | Created | Ten dokument |
|
||||
| `wp-content/plugins/carei-reservation/includes/class-admin-panel.php` | Modified | Submenu page, handler POST, get_protection_packages() static, rozszerzenie save_reservation() o protection_package meta, wiersz w meta boxie |
|
||||
| `wp-content/plugins/carei-reservation/includes/class-rest-proxy.php` | Modified | Rejestracja GET /protection-packages, callback get_protection_packages() z filtrem active |
|
||||
| `wp-content/plugins/carei-reservation/includes/class-elementor-widget.php` | Modified | Nowy #carei-protection-packages-container + .carei-form__protection-divider nad #carei-insurance-container |
|
||||
| `wp-content/plugins/carei-reservation/assets/js/carei-reservation.js` | Modified | protectionPackages state, loadProtectionPackages(), renderProtectionPackages() z radio + deselect, getSelectedProtectionPayload(), buildBookingComments(), integracja w showSummaryOverlay (details + table + totals) |
|
||||
| `wp-content/plugins/carei-reservation/assets/css/carei-reservation.css` | Modified | Style kafelków pakietów (flex row, name blue, price gray right, radio 16×16 accent-color, separator dashed), mobile stack |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Pakiet NIE w priceItems Softra | HTTP 400 przy próbie wysłania fałszywego ID — Softra waliduje wobec swojej bazy | Wymagane osobne kanały: comments (widoczne pracownikom wypożyczalni), protectionPackage payload (zapis WP), summary UI (widoczne klientowi) |
|
||||
| UX: brak totala × days w kafelku | Klient poprosił o spójność z extras (tylko "X zł/doba" szary po prawej), total dopiero w summary | Prostszy kafelek, `updateProtectionTotals()` usunięty jako martwy kod, listenery dat oczyszczone |
|
||||
| Native radio + accent-color | Spójne z reszta formularza, user prosił o widoczny wybór | Minimalny CSS, natywna accessibility, JS tylko dla deselect |
|
||||
| Plan 13-01 superseded zamiast skasowany | Historia decyzji klienta zachowana dla przyszłego audytu | 13-01-PLAN.md pozostaje w repo z notatką supersession w ROADMAP |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Auto-fixed | 2 | Konieczne korekty po błędach runtime |
|
||||
| Scope additions | 1 | Feedback UX w trakcie: prostszy kafelek |
|
||||
| Deferred | 0 | — |
|
||||
|
||||
**Total impact:** Wszystkie deviations wynikły z feedback usera w trakcie APPLY (3 iteracje UI + 1 fix błędu API). Żadna nie wychodziła poza cel planu.
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [API] HTTP 400 z Softra przy wysyłaniu pakietu jako priceItem**
|
||||
- **Found during:** Task 3 (frontend JS) po wdrożeniu i kliknięciu "Pokaż podsumowanie"
|
||||
- **Issue:** `priceItems` zawierał `id: 'protection_soft'` — Softra nie zna tego ID i zwracała 400 Bad Request
|
||||
- **Fix:** Usunięto pakiet z `getSelectedExtrasForApi()`, dodano jako osobną linię w `comments` booking (`buildBookingComments()`) oraz jako wiersz w summary frontend (details list + price table + totals), grandGross = softraGross + protectionTotal
|
||||
- **Files:** `carei-reservation.js`
|
||||
- **Verification:** User potwierdził brak błędu + widoczność pakietu w summary
|
||||
|
||||
**2. [UX] Pakiet niewidoczny w liście "Wybrane opcje" summary**
|
||||
- **Found during:** Weryfikacja po fixie HTTP 400
|
||||
- **Issue:** `summaryDetails` iterował po `selectedExtras` z `getSelectedExtrasForApi()`, który nie zawiera już pakietu
|
||||
- **Fix:** Dodany warunkowy wiersz w liście używający `getSelectedProtectionPayload()` z formatem analogicznym do extras (`X zł/doba × N = total zł`)
|
||||
- **Files:** `carei-reservation.js`
|
||||
- **Verification:** User potwierdził widoczność
|
||||
|
||||
### Scope Additions
|
||||
|
||||
**1. [UX] Uproszczenie kafelka (usunięcie total × days z popupu)**
|
||||
- User request podczas APPLY: "pokazuj tak jak ceny pozostałych elementów, czyli po prawej wszystko szarym kolorem i bez sumowania za liczbę dni, to dopiero w kolejnym widoku podsumowania"
|
||||
- Efekt: `updateProtectionTotals()` usunięte jako martwy kod, listenery dat oczyszczone, CSS kafelków dostosowany do wzorca `.carei-form__extra-card`
|
||||
|
||||
### Deferred Items
|
||||
|
||||
None.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
| Issue | Resolution |
|
||||
|-------|------------|
|
||||
| Softra HTTP 400 | Refactor w locie — pakiet wyłączony z priceItems, dostarczany przez comments + meta + summary UI (patrz Auto-fixed #1) |
|
||||
| Radio niewidoczny (pierwotnie hidden) | User request: "brakuje checkboxa wyboru wersji ubezpieczenia" → native radio w row z accent-color |
|
||||
| Cena nie pogrubiona | User request → usunięcie override `font-weight: 400` na `.carei-form__protection-package__price strong` |
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Milestone v0.5 zamykany (Phase 13 ✅ + Phase 14 ✅ = 100%)
|
||||
- Wzorzec "WP-managed dane + REST public endpoint + integracja z summary" gotowy do reuse w kolejnych niezależnych od Softra customizacjach
|
||||
- Admin ma pełną kontrolę nad cenami bez wymagań modyfikacji Softra
|
||||
|
||||
**Concerns:**
|
||||
- VAT dla pakietu traktowany jako brutto bez rozbicia netto/VAT — wystarczy dla MVP, ale jeśli klient będzie wymagał księgowego rozbicia, trzeba dodać `vatRate` w adminie i kalkulację
|
||||
- Comments Softra ma limit długości nieznany — w przyszłości rozważyć dedykowany field w integracji, jeśli Softra doda taki
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 13-protection-packages, Plan: 02*
|
||||
*Completed: 2026-04-20*
|
||||
Reference in New Issue
Block a user