Files
carei.pagedev.pl/.paul/phases/17-bilingual-packages-and-softra-errors/17-01-PLAN.md
2026-04-22 22:00:50 +02:00

306 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 17-bilingual-packages-and-softra-errors
plan: 01
type: execute
wave: 1
depends_on: ["16-01"]
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-softra-api.php
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js (drobne — użycie istniejących reject* kluczy)
autonomous: false
delegation: off
---
<objective>
## Goal
(1) Panel administratora pakietów ochronnych SOFT/PREMIUM dostaje dodatkowe pola `name_en` i `description_en`. REST endpoint `/protection-packages` zwraca wariant zgodny z aktualnym locale (`get_locale()` lub parametr `?lang=`): PL → pola bazowe, EN → pola `_en` z fallbackiem do bazowych gdy puste. (2) Słownik mapowania ~1215 typowych komunikatów Softra API (PL stringi zwracane przez zewnętrzny system) na lokalizowane klucze — warstwa w `Carei_Softra_API` / `Carei_REST_Proxy` podmienia `message` w `WP_Error`/response na tekst wg current locale.
## Purpose
Bez Phase 17 użytkownik na wersji EN widziałby (1) polskie nazwy pakietów ochronnych wprowadzone przez admina w panelu WP, (2) polskie komunikaty z Softra API przy konflikcie rezerwacji / braku pojazdu / błędzie walidacji. Phase 17 domyka jedyne dwa źródła „obcych" polskich tekstów pozostałych po Phase 16 — po niej całość UI jest dwujęzyczna bez wyjątków.
## Output
- Panel `Rezerwacje → Pakiety ochronne`: każdy pakiet (SOFT/PREMIUM) ma 4 pola tekstowe zamiast 2 — `name` + `name_en`, `description` + `description_en` (cena `pricePerDay` pozostaje jedna, wspólna)
- Option `carei_protection_packages` w DB ma nową strukturę: `soft: {name, name_en, description, description_en, pricePerDay, enabled}`, analogicznie premium
- REST `/protection-packages` zwraca wariant zlokalizowany wg `determine_locale()` — klucze `name`/`description` w odpowiedzi są już właściwe dla języka (EN lub PL), frontend nie musi wiedzieć nic o wariantach
- Słownik mapowania w `Carei_Softra_API` (nowa metoda `map_error_message( $pl_message )`) — zwraca sparowany klucz tłumaczenia z textdomain `carei-reservation` dla znanych komunikatów, albo oryginał jeśli brak mapowania
- Komunikaty w REST response / `WP_Error` przechodzą przez filtr mapowania przed zwróceniem do frontu
- Zero regresji w wersji PL — wszystkie pakiety i błędy wyświetlają się identycznie jak po Phase 16
</objective>
<context>
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/phases/13-protection-packages/13-02-SUMMARY.md
@.paul/phases/16-i18n-plugin-refactor/16-01-SUMMARY.md
@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-softra-api.php
@wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
</context>
<acceptance_criteria>
## AC-1: Panel pakietów obsługuje pola _en
```gherkin
Given administrator wchodzi w `wp-admin Rezerwacje Pakiety ochronne`
When wypełnia pola `Nazwa` / `Nazwa (EN)` / `Opis` / `Opis (EN)` dla SOFT i PREMIUM i klika "Zapisz"
Then wszystkie 4 pola tekstowe per pakiet są zapisane w option `carei_protection_packages`
And po odświeżeniu strony formularz pokazuje zapisane wartości (w tym EN)
And walidacja/sanityzacja jest konsystentna między polami PL i EN
And brak EN (puste pole `name_en`/`description_en`) jest poprawny to oznacza fallback do PL"
```
## AC-2: REST endpoint zwraca wariant per locale
```gherkin
Given w DB są zapisane pakiety z polami PL i EN
When frontend PL woła `/wp-json/carei/v1/protection-packages` (locale = pl_PL)
Then odpowiedź zawiera `soft.name` i `soft.description` z wariantu PL
And analogicznie dla premium
When frontend EN (Polylang język EN) woła ten sam endpoint
Then odpowiedź zawiera `soft.name` = wartość `name_en` (lub PL jeśli pole EN puste)
And `soft.description` = `description_en` z fallbackiem
And struktura odpowiedzi nie zmienia kluczy (`name`, `description`, `pricePerDay`, `enabled`) tylko treści
And frontend JS nie wymaga żadnej zmiany logicznej (oprócz ewentualnego `?lang=` jeśli Polylang nie ustawia locale serwerowo)
```
## AC-3: Błędy Softra mapowane na lokalizowane stringi
```gherkin
Given użytkownik próbuje zarezerwować pojazd niedostępny w danym terminie
When Softra API zwraca message "Brak dostępnego pojazdu w wybranym terminie" (lub podobny)
Then `Carei_Softra_API::map_error_message()` rozpoznaje string
And zwraca lokalizowany wariant zgodny z current locale (`__('rejectCarNotFound', ...)` lub bezpośredni tekst EN)
And REST response `message` zawiera tekst w języku zgodnym z UI
And dla nieznanych komunikatów przepuszcza oryginał (graceful fallback)
And słownik pokrywa co najmniej 12 typowych komunikatów (lista w Task 2)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Panel admina — pola name_en / description_en + zapis/odczyt</name>
<files>wp-content/plugins/carei-reservation/includes/class-admin-panel.php</files>
<action>
1. Zaktualizuj `get_protection_packages_defaults()` — dodaj domyślne klucze `name_en` i `description_en` per pakiet (wartości puste albo angielskie defaulty typu `'Protection SOFT'` / `'Protection PREMIUM'`).
2. Zaktualizuj `get_protection_packages()` — metodę czyszczenia/merge z defaultami, żeby obsłużyła nowe pola (użytkownicy z starą strukturą dostają puste EN, bez crasha).
3. W `render_protection_packages_page()`:
- Dla każdego pakietu (SOFT, PREMIUM) dodać pod polami PL dwa kolejne wiersze:
- `<input type="text" name="packages[soft][name_en]">` z labelką `esc_html__( 'Nazwa (EN)', 'carei-reservation' )`
- `<textarea name="packages[soft][description_en]">` z labelką `esc_html__( 'Opis (EN)', 'carei-reservation' )`
- Wizualnie oddzielone (np. `<small>` note: „puste = fallback do wersji polskiej")
- Placeholdery w `esc_attr__()`
4. W `handle_protection_packages_save()`:
- Sanitize `name_en` przez `sanitize_text_field()`
- Sanitize `description_en` przez `sanitize_textarea_field()`
- Zapisz oba pola w tej samej strukturze co PL
- Nie ruszaj walidacji `pricePerDay` ani `enabled` — bez zmian
5. Nie zmieniaj slugów meta, endpointu REST (Task 2), żadnej logiki biznesowej poza polem form.
Unikaj:
- Dodawania nowego mechanizmu po stronie DB (np. osobnej opcji dla EN) — wszystko w istniejącym `carei_protection_packages`
- Tłumaczenia opisu na bieżąco w panelu admin — admin wpisuje ręcznie EN
- Refaktoru istniejącej struktury defaulti — tylko DODAJ klucze
</action>
<verify>
1. Otwórz `wp-admin → Rezerwacje → Pakiety ochronne` — widoczne 4 pola tekstowe per pakiet (PL name, EN name, PL description, EN description) + cena + enabled
2. Wpisz testowe wartości w EN, zapisz → po odświeżeniu zachowane
3. `get_option( 'carei_protection_packages' )` w wp-admin → Tools → Site Health → Info (lub `wp db query` na locale) zawiera nowe klucze `name_en`, `description_en`
4. `php -l class-admin-panel.php` → No syntax errors
5. Stary kod readujący `$packages['soft']['name']` dalej działa (brak BC break)
</verify>
<done>AC-1 satysfakcjonowane.</done>
</task>
<task type="auto">
<name>Task 2: REST endpoint per-locale + mapowanie błędów Softra</name>
<files>
wp-content/plugins/carei-reservation/includes/class-rest-proxy.php,
wp-content/plugins/carei-reservation/includes/class-softra-api.php
</files>
<action>
**Część A — REST `/protection-packages` zwraca wariant zlokalizowany:**
1. W `class-rest-proxy.php` odnajdź handler endpointu `/protection-packages` (prawdopodobnie metoda typu `get_protection_packages()`).
2. Dodaj helper `resolve_locale()`:
```php
private function resolve_locale( $request ) {
$lang = $request->get_param( 'lang' );
if ( $lang && in_array( strtolower( $lang ), array( 'pl', 'en' ), true ) ) {
return strtolower( $lang );
}
$locale = function_exists( 'determine_locale' ) ? determine_locale() : get_locale();
return ( 0 === strpos( $locale, 'en' ) ) ? 'en' : 'pl';
}
```
- Polylang przy żądaniach REST powinien ustawić locale automatycznie (filter `locale` lub `determine_locale`). Gdyby nie — frontend może dopisać `?lang=en` jawnie (fallback). JS zostaje bez zmian jeśli Polylang dobrze współpracuje z WP REST.
3. W handlerze endpointu: po pobraniu `$packages = Carei_Admin_Panel::get_protection_packages()`:
- Dla lokalu `'en'`: podmień `$pkg['name']` na `$pkg['name_en']` gdy niepuste, inaczej pozostaw `$pkg['name']`. Analogicznie `description`.
- Dla lokalu `'pl'`: bez zmian.
- Usuń z odpowiedzi klucze `name_en`/`description_en` (frontend nie musi ich widzieć — unika leakowania i mylenia schematu).
4. Odpowiedź REST zachowuje schemat: `{ soft: { name, description, pricePerDay, enabled }, premium: {...} }`.
**Część B — Mapowanie błędów Softra:**
5. W `class-softra-api.php` dodaj nową public static method:
```php
public static function map_error_message( $original_message ) {
if ( ! is_string( $original_message ) || '' === trim( $original_message ) ) {
return $original_message;
}
$dict = array(
'Brak dostępnego pojazdu w wybranym terminie' => __( 'Brak dostępnego pojazdu w wybranym terminie. Zmień daty lub segment.', 'carei-reservation' ),
'Nieprawidłowy zakres dat' => __( 'Nieprawidłowy zakres dat', 'carei-reservation' ),
'Nie znaleziono oddziału' => __( 'Nie znaleziono oddziału', 'carei-reservation' ),
'Klient o tych danych już istnieje' => __( 'Klient o tych danych już istnieje w systemie', 'carei-reservation' ),
'Nieprawidłowy numer PESEL' => __( 'Nieprawidłowy numer PESEL', 'carei-reservation' ),
'Cennik wygasł' => __( 'Cennik wygasł. Odśwież formularz i spróbuj ponownie.', 'carei-reservation' ),
'Token wygasł' => __( 'Sesja wygasła. Odśwież stronę.', 'carei-reservation' ),
'Nieprawidłowe dane logowania' => __( 'Błąd autoryzacji API. Skontaktuj się z administratorem.', 'carei-reservation' ),
'Brak uprawnień' => __( 'Brak uprawnień do wykonania operacji.', 'carei-reservation' ),
'Błąd serwera' => __( 'Błąd serwera. Spróbuj ponownie za chwilę.', 'carei-reservation' ),
'Przekroczono limit rezerwacji' => __( 'Przekroczono limit rezerwacji dla tego klienta.', 'carei-reservation' ),
'Nieprawidłowy numer telefonu' => __( 'Podaj poprawny numer telefonu (min. 9 cyfr).', 'carei-reservation' ),
'Wymagane pole' => __( 'To pole jest wymagane.', 'carei-reservation' ),
);
// Exact match
if ( isset( $dict[ $original_message ] ) ) {
return $dict[ $original_message ];
}
// Fuzzy: prefix match (Softra bywa mało przewidywalny z końcówkami)
foreach ( $dict as $pl_key => $translated ) {
if ( 0 === stripos( $original_message, $pl_key ) ) {
return $translated;
}
}
return $original_message; // graceful fallback
}
```
- Zasada: `__()` przechodzi przez textdomain `carei-reservation` → dla locale EN pobiera tłumaczenie z `.mo` (Phase 18). W PL zwraca ten sam tekst co msgid (co jest OK — zachowuje polski oryginał).
6. Zintegruj mapowanie:
- W miejscach gdzie `Carei_Softra_API` zwraca błąd (np. metoda `make_booking`, `get_car_classes`, itp.) — po odebraniu `$response['error']['message']` lub `$response['message']` (sprawdź strukturę w istniejącym kodzie) zawiń przez `self::map_error_message( $msg )` przed utworzeniem `WP_Error` / przed zwrotem.
- Jeśli API używa `WP_Error` już z warstwy `rest-proxy` — zaktualizuj tam (przechwyć message, mapuj, przekaż dalej).
**Część C — Frontend (drobna zmiana w JS):**
7. W `carei-reservation.js` w miejscach gdzie łapiesz `err.message` z odpowiedzi API przy niepowodzeniu rezerwacji — jeśli obecnie wyświetlasz surowy message, zostaw bez zmian (backend już mapuje). Jeśli masz heurystykę kategoryzacji po kluczach (np. "no car available" → pokaż specjalny alert), rozważ aktualizację. W MINIMUM: upewnij się że fallbacki `rejectCarNotFound` itd. w `careiI18n` są używane gdzie trzeba. Prawdopodobnie zmiana zero/minimal.
8. Jeśli frontend musi jawnie dopisać `?lang=` przy requeście `/protection-packages` (bo Polylang nie ustawia locale REST automatycznie) — dodaj to w `loadProtectionPackages()`:
```js
var lang = (document.documentElement.lang || '').toLowerCase().indexOf('en') === 0 ? 'en' : 'pl';
fetch(REST_URL + 'protection-packages?lang=' + lang, {...})
```
Unikaj:
- Tłumaczenia komunikatów po stronie JS — wszystko leci z PHP (`__()` rozwiązuje per locale)
- Wprowadzania nowego schemat odpowiedzi (`name_pl` / `name_en` w payloadzie) — odpowiedź zawsze ma `name` w właściwym języku
- Nadpisywania obecnej logiki reject — tylko dodaj warstwę mapowania
</action>
<verify>
1. `php -l` dla obu zmienionych plików → No syntax errors
2. W PL (Polylang = PL): `curl /wp-json/carei/v1/protection-packages` → `soft.name` = polska nazwa, `soft.description` = polski opis
3. W EN (Polylang = EN) albo `?lang=en`: `soft.name` = angielska nazwa (lub polska fallback gdy puste)
4. Odpowiedź REST NIE zawiera kluczy `name_en`/`description_en` (czyste API)
5. Wywołanie błędnej rezerwacji (niedostępny pojazd) → komunikat zwrócony do frontendu = tłumaczenie z `__()` zamiast surowego Softra-PL (w EN)
6. Nieznany komunikat Softra (spoza słownika) → passthrough bez zmian (graceful)
</verify>
<done>AC-2, AC-3 satysfakcjonowane.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
- Panel `wp-admin → Rezerwacje → Pakiety ochronne` ma teraz 4 pola tekstowe per pakiet (`name`, `name_en`, `description`, `description_en`) + cena + enabled
- Option `carei_protection_packages` przechowuje wersje PL i EN każdego pakietu
- REST `/protection-packages` zwraca wariant per locale (Polylang EN → pola `_en` z fallbackiem do PL)
- Słownik `Carei_Softra_API::map_error_message()` z 12+ wpisami + exact/fuzzy match, wbudowany w ścieżkę zwrotu błędów API
- Wersja PL — zero regresji, wersja EN — pakiety i błędy w języku EN (po załadowaniu .mo w Phase 18)
</what-built>
<how-to-verify>
1. Wypchnij zmienione 34 pliki przez SFTP
2. **Admin panel test:**
- `wp-admin → Rezerwacje → Pakiety ochronne`
- Sprawdź że są widoczne nowe pola: „Nazwa (EN)", „Opis (EN)" dla SOFT i PREMIUM
- Wypełnij testowo: SOFT EN name = „SOFT Protection", EN description = „Basic protection package..."
- PREMIUM EN name = „PREMIUM Protection", EN description = „Enhanced protection..."
- Kliknij „Zapisz" → odśwież stronę → wartości EN są zachowane
3. **Frontend PL (Polylang = PL):**
- Otwórz modal rezerwacji → sekcja „Pakiety ochronne"
- Kafelki SOFT/PREMIUM pokazują **polskie** nazwy i opisy (jak zapisane w polach PL)
- Cena `X.XX zł/doba` — bez zmian
4. **Frontend EN (Polylang = EN):**
- Przełącz język na EN w switcherze Polylang
- Otwórz modal rezerwacji → sekcja „Pakiety ochronne"
- Kafelki pokazują **angielskie** nazwy i opisy (wartości z pól `_en`)
- Jeśli któreś pole `_en` zostawiłeś puste → frontend powinien pokazać PL wariant (fallback)
5. **Test fallbacku:**
- Wróć do admin → pole EN jednego pakietu wyczyść
- Frontend EN → ten pakiet pokazuje polski oryginał (graceful)
6. **Error mapping test:**
- Wymuś błąd Softra (np. data w przeszłości albo niedostępny pojazd — jeśli API tak odpowiada)
- PL: komunikat w języku polskim (bez zmian)
- EN: komunikat po angielsku (po załadowaniu .mo w Phase 18) albo oryginalne PL jeśli Phase 18 jeszcze niedostępne
- Uwaga: **dopóki .po/.mo nie istnieją (Phase 18), `__()` zwraca oryginalny polski string w obu językach.** To jest OK — Phase 17 dostarcza infrastrukturę, Phase 18 dostarcza treść.
7. **DevTools Network:**
- `GET /wp-json/carei/v1/protection-packages` w PL → odpowiedź z PL
- `GET /wp-json/carei/v1/protection-packages?lang=en` → odpowiedź z EN
- Oba responsy nie zawierają pól `name_en`/`description_en` w payloadzie
8. **Brak regresji:**
- Cały flow rezerwacji PL działa jak po Phase 16 — nic się nie popsuło
**Kryterium przejścia:** Admin zapisuje i odczytuje EN pola, REST zwraca wariant per locale, error mapping działa (graceful fallback dla nieznanych), PL bez zmian.
</how-to-verify>
<resume-signal>Napisz "approved" lub opisz co nie działa.</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Schema `carei_reservation` CPT, meta keys (`_carei_*`), statusy w DB
- Logika JWT auth w `Carei_Softra_API` (cache, retry)
- Phase 13 struktura REST endpointu `/protection-packages` — klucze odpowiedzi (`name`, `description`, `pricePerDay`, `enabled`) pozostają, zmienia się treść per locale
- Phase 15 filtr Softra-insurance (drop) — nietknięty
- Phase 16 textdomain i wp_localize_script — tylko ROZSZERZAMY (dokładamy nowe klucze jeśli potrzeba), nie refaktorujemy
- Frontend JS — minimalne zmiany (ewentualnie `?lang=` parameter), bez refactoru flow
## SCOPE LIMITS
- Nie generujemy tłumaczeń `.po`/`.mo` — to Phase 18
- Nie dodajemy mechanizmu Polylang "String Translation" per-post-meta (nadkomplikacja) — option w DB z polami `_en` wystarczy dla 2 pakietów
- Nie rozszerzamy słownika Softra na >20 pozycji — 1215 pokrywa realne przypadki, resztę mapujemy iteracyjnie gdy się pojawią
- Nie tłumaczymy statusów rezerwacji w DB (`nowe`/`przeczytane`/`zrealizowane`) — tylko UI labels (już zrobione w Phase 16)
- Nie dotykamy Elementora ani treści stron
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] Panel admin pokazuje 4 pola per pakiet (PL + EN name, PL + EN description)
- [ ] Zapisanie EN w panelu → dane utrzymują się po reload
- [ ] REST `/protection-packages` w PL zwraca polskie, w EN zwraca angielskie (lub PL fallback)
- [ ] Odpowiedź REST nie zawiera `name_en`/`description_en` (czyste API)
- [ ] `Carei_Softra_API::map_error_message()` istnieje, słownik ma >= 12 wpisów
- [ ] Exact match + fuzzy prefix match działają
- [ ] Zero regresji w PL (human-verify)
- [ ] AC-1, AC-2, AC-3 przeszły weryfikację
</verification>
<success_criteria>
- Wszystkie 2 auto tasks zakończone
- Checkpoint human-verify zatwierdzony ("approved")
- PL bez regresji
- EN: pakiety pokazują wartości z pól `_en` (lub PL fallback)
- Infrastruktura mapowania błędów Softra gotowa (tłumaczenia pojawią się po Phase 18)
</success_criteria>
<output>
Po zakończeniu: `.paul/phases/17-bilingual-packages-and-softra-errors/17-01-SUMMARY.md`
</output>