diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index e0eac56..1644a22 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -14,7 +14,7 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online - |-----------|-------| | Version | 0.333 | | Status | Production | -| Last Updated | 2026-04-19 | +| Last Updated | 2026-04-20 | ## Requirements @@ -31,6 +31,7 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online - - [x] Ochrona przed podwójnym składaniem zamówienia - [x] Domain-Driven Architecture (migracja z legacy zakończona) - [x] Szybka edycja custom_label_0..4 na liscie produktow admina (toggle sesyjny + autocomplete) +- [x] Poprawna kalkulacja kosztu transportu na /koszyk-podsumowanie (fix delivery_free bez uwzglednienia progu) ### Active (In Progress) @@ -81,12 +82,13 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online - | Własny silnik zamiast frameworka | Pełna kontrola, brak narzutów | - | Active | | `id` w tabbed FormEdit przez `hiddenFields` | Zapobiega insert zamiast update przy edycji encji | 2026-04-18 | Active | | Inline custom labels w product list przez sesyjny toggle | Szybszy workflow dla Google XML bez wejscia w edycje produktu | 2026-04-19 | Active | +| Kalkulacja kosztu transportu na /koszyk-podsumowanie w kontrolerze (nie w szablonie) | Spojnosc logiki progu darmowej dostawy miedzy /koszyk i /koszyk-podsumowanie | 2026-04-20 | Active | ## Success Metrics | Metric | Target | Current | Status | |--------|--------|---------|--------| -| Testy | >800 | 821 | On track | +| Testy | >800 | 834 | On track | | Pokrycie architektury DDD | 100% | 100% | Achieved | ## Tech Stack @@ -115,4 +117,4 @@ Quick Reference: --- *PROJECT.md - Updated when requirements or context change* -*Last updated: 2026-04-19 after Phase 16* +*Last updated: 2026-04-20 after Phase 17* diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index fc9cb7d..e40d850 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -37,6 +37,7 @@ Status: Planning | 8 | Apilo orders not sending — diagnoza i naprawa | 1 | Done | 2026-03-16 | | 9 | Apilo email notification + infinite retry | 1 | Done | 2026-03-19 | | 15 | Scontainers edit saves as new record | 1 | Done | 2026-04-18 | +| 17 | Cart summary transport cost fix | 1 | Done | 2026-04-20 | ## Feature @@ -118,5 +119,11 @@ Status: Planning **Scope:** Dodac przycisk "Pokaz etykiety niestandardowe" obok "Dodaj produkt", zapisywac jego stan w sesji, pokazac 5 pol custom label pod nazwa produktu, zapisac wartosci do bazy i zapewnic podpowiedzi z juz istniejacych wartosci. +### Phase 17 - Cart summary transport cost fix + +**Problem:** Na /koszyk-podsumowanie kazdy transport z flaga `delivery_free = 1` pokazywany jest za 0,00 zl, niezaleznie od tego czy koszyk osiagnal prog darmowej dostawy `settings.free_delivery`. Szablon summary-view.php sprawdza tylko flage, nie wartosc koszyka. Suma koncowa zamowienia jest zaniżona. + +**Scope:** Przekazac z `ShopBasketController::summaryView()` do szablonu wyliczony `transport_cost_effective` i flage `free_delivery_applies` uwzgledniajaca prog. Zaktualizowac summary-view.php aby uzywal tych kluczy zamiast surowej flagi `delivery_free`. Test jednostkowy dla logiki wyliczenia. + --- -*Last updated: 2026-04-19 (Phase 16 complete)* +*Last updated: 2026-04-20 (Phase 17 complete)* diff --git a/.paul/STATE.md b/.paul/STATE.md index 43a6997..affd51e 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -5,19 +5,19 @@ See: .paul/PROJECT.md (updated 2026-04-18) **Core value:** Właściciel sklepu ma pełną kontrolę nad sprzedażą online w jednym systemie pisanym od podstaw, bez narzutów zewnętrznych platform. -**Current focus:** Phase 16 complete - loop closed +**Current focus:** Phase 17 complete - loop closed ## Current Position -Milestone: Feature -Phase: 16 of 16 (Product list custom labels quick edit) - Complete -Plan: 16-01 complete -Status: UNIFY complete, ready for next PLAN loop -Last activity: 2026-04-19 - Closed loop for .paul/phases/16-product-list-custom-labels/16-01-PLAN.md +Milestone: Hotfix +Phase: 17 of 17 (Cart summary transport cost fix) - Complete +Plan: 17-01 complete +Status: UNIFY complete, ready for next PLAN loop (transition-phase pending) +Last activity: 2026-04-20 - Closed loop for .paul/phases/17-cart-summary-transport-cost-fix/17-01-PLAN.md Progress: - Milestone: [##########] 100% -- Phase 16: [##########] 100% +- Phase 17: [##########] 100% ## Loop Position @@ -42,10 +42,17 @@ Phase 13: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-25] Phase 14: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-16] Phase 15: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-18] Phase 16: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-19] +Phase 17: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-20] ``` ## Accumulated Context ### Decisions +- 2026-04-20: Phase 17 loop closed with SUMMARY at .paul/phases/17-cart-summary-transport-cost-fix/17-01-SUMMARY.md +- 2026-04-20: Transition-phase git commit for Phase 17 not executed in this UNIFY run (deferred) +- 2026-04-20: Phase 17 APPLY complete - human-verify checkpoint approved, 834 testow zielonych (6 nowych) +- 2026-04-20: Created Phase 17 plan at .paul/phases/17-cart-summary-transport-cost-fix/17-01-PLAN.md +- 2026-04-20: Phase 17 bug root cause - summary-view.php blindly shows 0 zl gdy transport.delivery_free=1 bez sprawdzenia progu settings.free_delivery +- 2026-04-20: Phase 17 fix - nowa chroniona metoda ShopBasketController::calculateTransportCostForSummary zwraca transport_cost_effective + free_delivery_applies; szablon uzywa tych kluczy zamiast delivery_free - 2026-04-19: Created Phase 16 plan at .paul/phases/16-product-list-custom-labels/16-01-PLAN.md - 2026-04-19: Phase 16 scope includes session toggle + inline custom_label_0..4 edit + suggestions on product list - 2026-04-19: Override approved by user - proceeded without required /feature-dev skill in Phase 16 APPLY @@ -90,9 +97,9 @@ None. ## Session Continuity -Last session: 2026-04-19 -Stopped at: Phase 16 complete, loop closed -Next action: Start next milestone or create next phase plan -Resume file: .paul/phases/16-product-list-custom-labels/16-01-SUMMARY.md +Last session: 2026-04-20 +Stopped at: Phase 17 complete, loop closed +Next action: Start next milestone or create next phase plan (transition-phase commit pending) +Resume file: .paul/phases/17-cart-summary-transport-cost-fix/17-01-SUMMARY.md --- *STATE.md — Updated after every significant action* diff --git a/.paul/changelog/2026-04-20.md b/.paul/changelog/2026-04-20.md new file mode 100644 index 0000000..d76b931 --- /dev/null +++ b/.paul/changelog/2026-04-20.md @@ -0,0 +1,19 @@ +# 2026-04-20 + +## Co zrobiono + +- [Phase 17, Plan 01] Naprawa kosztu transportu na /koszyk-podsumowanie — transport z flaga delivery_free=1 pokazuje teraz rzeczywisty koszt ponizej progu settings.free_delivery, a 0,00 zl dopiero po osiagnieciu progu +- Dodana chroniona metoda `ShopBasketController::calculateTransportCostForSummary` wyliczajaca `transport_cost_effective` i `free_delivery_applies` +- Szablon `templates/shop-basket/summary-view.php` uzywa tych kluczy zamiast surowej flagi `delivery_free` +- Nowy plik testow `ShopBasketControllerSummaryViewTest` (6 testow, 12 assertions) +- Pelna suita PHPUnit: 834/834 OK (2318 assertions) + +## Zmienione pliki + +- `autoload/front/Controllers/ShopBasketController.php` +- `templates/shop-basket/summary-view.php` +- `tests/Unit/front/Controllers/ShopBasketControllerSummaryViewTest.php` +- `.paul/phases/17-cart-summary-transport-cost-fix/17-01-PLAN.md` +- `.paul/phases/17-cart-summary-transport-cost-fix/17-01-SUMMARY.md` +- `.paul/STATE.md` +- `.paul/ROADMAP.md` diff --git a/.paul/docs/TECH_CHANGELOG.md b/.paul/docs/TECH_CHANGELOG.md index e502711..58bf49c 100644 --- a/.paul/docs/TECH_CHANGELOG.md +++ b/.paul/docs/TECH_CHANGELOG.md @@ -2,6 +2,13 @@ > Chronologiczny log zmian technicznych — co i dlaczego. +## v0.349 (2026-04-20) + +- Naprawiono wyswietlanie kosztu transportu na /koszyk-podsumowanie: transporty z `delivery_free=1` pokazuja teraz rzeczywisty koszt ponizej progu `settings.free_delivery`, a 0,00 zl dopiero po osiagnieciu progu (spojnie z lista na /koszyk). +- Dodano chroniona metode `ShopBasketController::calculateTransportCostForSummary()` wyliczajaca `transport_cost_effective` + `free_delivery_applies` — logika widokowa przeniesiona z szablonu do kontrolera. +- Szablon `templates/shop-basket/summary-view.php` uzywa nowych kluczy zamiast sprawdzania surowej flagi `delivery_free`. +- Dodano 6 testow jednostkowych (`ShopBasketControllerSummaryViewTest`) pokrywajacych AC + edge cases (prog rowny, prog 0, transport null). Suita: 834 testy / 2318 assertions. + ## v0.348 (2026-04-19) - Dodano przełącznik widoczności etykiet niestandardowych na liście produktów w panelu admina, z zapisem stanu w sesji. diff --git a/.paul/docs/TODO.md b/.paul/docs/TODO.md index f9811d5..e9bb9d3 100644 --- a/.paul/docs/TODO.md +++ b/.paul/docs/TODO.md @@ -3686,3 +3686,5 @@ Dodać możliwość ustawienia limitu znaków w wiadomościach do produktu - [ ] [MINOR] autoload/admin/Controllers/ShopProductController.php:971 - Rename function "generate_sku_code" to match the regular expression ^[a-z][a-zA-Z0-9]*$. (php:S100) - [ ] [MINOR] autoload/admin/Controllers/ShopProductController.php:989 - Rename function "product_combination" to match the regular expression ^[a-z][a-zA-Z0-9]*$. (php:S100) + +## SonarQube - v0.349 - brak nowych issues diff --git a/.paul/phases/17-cart-summary-transport-cost-fix/17-01-PLAN.md b/.paul/phases/17-cart-summary-transport-cost-fix/17-01-PLAN.md new file mode 100644 index 0000000..595444e --- /dev/null +++ b/.paul/phases/17-cart-summary-transport-cost-fix/17-01-PLAN.md @@ -0,0 +1,191 @@ +--- +phase: 17-cart-summary-transport-cost-fix +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - autoload/front/Controllers/ShopBasketController.php + - templates/shop-basket/summary-view.php + - tests/Unit/front/Controllers/ShopBasketControllerSummaryViewTest.php +autonomous: false +delegation: off +--- + + +## Goal +Naprawic blad na stronie /koszyk-podsumowanie, gdzie wybrana forma wysylki oraz laczna kwota zamowienia pokazywane sa za 0,00 zl, mimo ze koszyk nie osiagnal progu darmowej dostawy. + +## Purpose +Klient widzi nieprawidlowe podsumowanie zamowienia. Koszt transportu w szablonie summary-view.php jest redukowany do zera zawsze, gdy transport ma flage `delivery_free = 1`, bez sprawdzenia czy wartosc koszyka przekroczyla prog `$settings['free_delivery']`. W efekcie klient widzi "0,00 zl" i zaniżona sume zamowienia. Po zlozeniu zamowienia dane w bazie i ostatecznej cenie moga sie roznic, co psuje zaufanie i ksiegowosc. + +## Output +- ShopBasketController::summaryView() przekazuje do szablonu koszt transportu po uwzglednieniu progu darmowej dostawy (nowy klucz `transport_cost_effective` oraz `free_delivery_applies`). +- Szablon summary-view.php pokazuje koszt transportu i sume koncowa na podstawie tych kluczy zamiast surowej flagi `delivery_free`. +- Nowy test jednostkowy potwierdza logike wyliczania kosztu w kontrolerze dla 3 scenariuszy (basket ponizej progu, basket rowny progowi, transport bez `delivery_free`). + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md + +## Source Files +@autoload/front/Controllers/ShopBasketController.php +@autoload/Domain/Transport/TransportRepository.php +@autoload/Domain/Basket/BasketCalculator.php +@templates/shop-basket/summary-view.php +@templates/shop-basket/basket-transport-methods.php + +## Powiazane pliki (do odwolania) +- settings.free_delivery w `pp_settings` (globalny prog darmowej dostawy) +- Helpers::normalize_decimal / Helpers::decimal (format kwot) + + + + +## AC-1: Transport z flaga delivery_free ponizej progu pokazuje rzeczywisty koszt +```gherkin +Given transport ma `delivery_free = 1`, cost = 15.00 zl, a `$settings['free_delivery']` = 300 zl +And wartosc koszyka (po kuponie) wynosi 150 zl +When klient wchodzi na /koszyk-podsumowanie +Then linia transportu pokazuje "15,00 zl" +And laczna kwota zamowienia zawiera te 15,00 zl +``` + +## AC-2: Transport z flaga delivery_free powyzej progu pokazuje 0,00 zl +```gherkin +Given transport ma `delivery_free = 1`, cost = 15.00 zl, a `$settings['free_delivery']` = 300 zl +And wartosc koszyka (po kuponie) wynosi 350 zl +When klient wchodzi na /koszyk-podsumowanie +Then linia transportu pokazuje "0,00 zl" +And laczna kwota nie zawiera kosztu transportu +``` + +## AC-3: Transport bez flagi delivery_free zawsze pokazuje swoj koszt +```gherkin +Given transport ma `delivery_free = 0`, cost = 25.00 zl +And wartosc koszyka wynosi 500 zl (powyzej dowolnego progu) +When klient wchodzi na /koszyk-podsumowanie +Then linia transportu pokazuje "25,00 zl" +And laczna kwota zawiera te 25,00 zl +``` + +## AC-4: Suma testow PHPUnit nie maleje, nowy test zielony +```gherkin +Given istniejacy zestaw testow `./test.ps1` +When uruchamiam pelna suite +Then nowy test `ShopBasketControllerSummaryViewTest` przechodzi +And zadne istniejace testy nie zaczynaja failowac +``` + + + + + + + Task 1: Przekaz wyliczony koszt transportu do summary-view z kontrolera + autoload/front/Controllers/ShopBasketController.php + + W metodzie `summaryView()` (ok. linia 270): + - Po pobraniu `$transport` (findActiveByIdCached) wylicz kwote koszyka po kuponie uzywajac `\Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon )` - tak jak robi to `transportMethodsFront`. + - Wczytaj `$settings['free_delivery']` z globala `$settings`. + - Ustaw `free_delivery_applies = false` gdy transport nie istnieje; w przeciwnym razie `true` wtedy i tylko wtedy gdy `$transport['delivery_free'] == 1` ORAZ `normalize_decimal($products_summary) >= normalize_decimal($settings['free_delivery'])`. + - Wylicz `transport_cost_effective` = `free_delivery_applies ? 0.0 : (float)$transport['cost']`. + - Do `Tpl::view` przekaz dodatkowe klucze `transport_cost_effective` i `free_delivery_applies`. + Nie zmieniaj istniejacych kluczy (transport, payment_method itd.) zeby nie zepsuc innych uzyc szablonu. + Nie modyfikuj logiki tokenu zamowienia ani guardow. + Unikaj: dodawania nowych metod do TransportRepository (kalkulacja nalezy do warstwy koszyka, nie transportu). + + Recznie odczytaj plik, upewnij sie ze dane sa w tablicy Tpl::view i sa uzywane deterministycznie dla transport === null. + AC-1, AC-2 i AC-3 zaspokojone po stronie danych; AC-4 kontrolera. + + + + Task 2: Zaktualizuj summary-view.php aby uzywal wyliczonych kluczy + templates/shop-basket/summary-view.php + + W bloku "basket-summary" (ok. linii 97-115): + - Zamien warunek `$this->transport['delivery_free'] == 1` na `$this->free_delivery_applies`. + - Zamiast `$this->transport['cost']` wyswietlaj `$this->transport_cost_effective` w galezi "else" oraz w kwocie koncowej. + - Linia koncowej kwoty (order-summary): `$this->free_delivery_applies ? decimal($summary) : decimal($summary + $this->transport_cost_effective)`. + - Zadbaj o poprawne wyswietlenie gdy `transport` jest `null` (skeleton: zachowaj stary fallback - brak kosztu dodawanego). + Nie modyfikuj pozostalych fragmentow (produkty, adres, GTM itd.). + Unikaj: duplikowania logiki progu w szablonie - szablon ma wyswietlac, nie liczyc. + + W szablonie nie wystepuje juz `$this->transport['delivery_free']` w tym bloku; nowe klucze sa uzyte dwukrotnie (linia transportu + suma). + AC-1, AC-2, AC-3 zaspokojone po stronie prezentacji. + + + + Task 3: Test jednostkowy dla logiki kontrolera + tests/Unit/front/Controllers/ShopBasketControllerSummaryViewTest.php + + Utworz nowy plik testow PHPUnit extending `PHPUnit\Framework\TestCase`. + Testuj publiczna metode pomocnicza lub kalkulacje w `summaryView()` przez refleksje/helper - preferowane: wyodrebnic wyliczenie do prywatnej metody i wyeksponowac prywatna metode przez ReflectionMethod (bez zmiany publicznego API). + Alternatywa: utworz w kontrolerze protected method `calculateTransportCostForSummary(array $transport = null, array $basket, $coupon, float $freeDeliveryThreshold): array` zwracajaca `['transport_cost_effective' => float, 'free_delivery_applies' => bool]` i pokryj ja testami bezposrednio. + Trzy scenariusze (AC-1, AC-2, AC-3) + czwarty: transport === null -> cost 0.0, applies false. + Mock `\Domain\Basket\BasketCalculator::summaryPrice` nie jest wymagany - podaj gotowa liczbe w tescie. + Test musi sie uruchamiac pod `./test.ps1`. + Unikaj: testowania na realnej bazie - stub Medoo zaslugujac na AAA. + + Uruchom `./test.ps1 tests/Unit/front/Controllers/ShopBasketControllerSummaryViewTest.php` - 4 testy zielone. Potem `./test.ps1` pelna suite - liczba testow >= 825 (pelna, bez regresji). + AC-4 zaspokojone. + + + + + Poprawka koszt transportu na /koszyk-podsumowanie. + + + 1. W panelu admina upewnij sie, ze co najmniej jedna metoda transportu ma `delivery_free = 1` i niezerowy `cost` (np. 15 zl). + 2. Ustaw `settings.free_delivery` na np. 300 zl. + 3. Dodaj do koszyka produkty o wartosci PONIZEJ progu (np. 150 zl). + 4. Wejdz na /koszyk, wybierz transport z `delivery_free = 1`, przejdz do /koszyk-podsumowanie. + 5. Potwierdz, ze linia transportu pokazuje "15,00 zl" (nie "0,00 zl") i suma zawiera ten koszt. + 6. Dolow koszyk do wartosci POWYZEJ progu (>300 zl), odswiez /koszyk-podsumowanie. + 7. Potwierdz, ze linia transportu pokazuje "0,00 zl" i suma NIE zawiera kosztu transportu. + 8. Wybierz transport bez `delivery_free = 1` (np. kurier 25 zl), potwierdz ze zawsze pokazuje 25,00 zl. + + Wpisz "approved" aby zakonczyc, lub opisz niezgodnosc do poprawy. + + + + + + +## DO NOT CHANGE +- autoload/Domain/Transport/TransportRepository.php (kalkulacja kosztu transportu juz jest w `transportMethodsFront`; nie duplikujemy logiki tam). +- autoload/Domain/Basket/BasketCalculator.php (wyliczenie wartosci koszyka pozostaje bez zmian). +- templates/shop-basket/basket-transport-methods.php (lista metod na /koszyk dziala poprawnie). +- Logika tokenu zamowienia w ShopBasketController (createOrderSubmitToken, consumeOrderSubmitToken). +- Struktura bazy danych (brak migracji). + +## SCOPE LIMITS +- Plan naprawia WYLACZNIE wyswietlanie kosztu na /koszyk-podsumowanie. +- Nie refaktoryzujemy summary-view.php poza blokiem transportu. +- Nie zmieniamy mechanizmu cache transportu. +- Nie dodajemy nowych ustawien/kolumn w bazie. + + + + +Przed zamknieciem planu: +- [ ] `./test.ps1` pelna suite zielona (wszystkie >=824 + 4 nowe testy). +- [ ] Recznie zweryfikowano 3 scenariusze na /koszyk-podsumowanie (checkpoint human-verify). +- [ ] W summary-view.php nie wystepuje juz `$this->transport['delivery_free']` w sekcji podsumowania. +- [ ] Nowy plik testu istnieje i jest w strukturze `tests/Unit/front/Controllers/`. +- [ ] Kod zgodny z PHP 7.4 (brak `match`, named arguments itd.). + + + +- Wszystkie 4 AC zaspokojone. +- Suita PHPUnit zielona bez regresji. +- Checkpoint human-verify zaakceptowany. +- Brak nowych ostrzezen/bledow w logach. + + + +Po zakonczeniu utworz `.paul/phases/17-cart-summary-transport-cost-fix/17-01-SUMMARY.md` + diff --git a/.paul/phases/17-cart-summary-transport-cost-fix/17-01-SUMMARY.md b/.paul/phases/17-cart-summary-transport-cost-fix/17-01-SUMMARY.md new file mode 100644 index 0000000..f1f6cbd --- /dev/null +++ b/.paul/phases/17-cart-summary-transport-cost-fix/17-01-SUMMARY.md @@ -0,0 +1,150 @@ +--- +phase: 17-cart-summary-transport-cost-fix +plan: 01 +subsystem: checkout +tags: [basket, transport, free-delivery, summary-view, php74] + +requires: + - phase: 13-basket-logging-ttl-token + provides: createOrderSubmitToken + TTL i logging w basketSave +provides: + - Poprawna kalkulacja kosztu transportu na /koszyk-podsumowanie + - Testowalna chroniona metoda ShopBasketController::calculateTransportCostForSummary +affects: [przyszle zmiany checkoutu, kupony, promocje darmowej dostawy] + +tech-stack: + added: [] + patterns: + - "Logika prezentacyjna kosztu transportu trzymana w kontrolerze, nie w szablonie" + - "Chronione metody pomocnicze testowane przez ReflectionMethod" + +key-files: + created: + - tests/Unit/front/Controllers/ShopBasketControllerSummaryViewTest.php + modified: + - autoload/front/Controllers/ShopBasketController.php + - templates/shop-basket/summary-view.php + +key-decisions: + - "Kalkulacja kosztu transportu zostaje w warstwie kontrolera (summaryView), nie w TransportRepository — Repository dostarcza dane, kontroler interpretuje je dla konkretnego widoku" + - "Metoda calculateTransportCostForSummary pozostaje protected i jest testowana przez Reflection (public API kontrolera bez zmian)" + +patterns-established: + - "Szablon summary-view otrzymuje gotowe klucze prezentacyjne (transport_cost_effective, free_delivery_applies) zamiast liczyc progi w locie" + +duration: ~25min +started: 2026-04-20T00:00:00Z +completed: 2026-04-20T00:25:00Z +--- + +# Phase 17 Plan 01: Cart summary transport cost fix — Summary + +**Na /koszyk-podsumowanie wybrany transport z flaga delivery_free=1 pokazuje teraz rzeczywisty koszt ponizej progu darmowej dostawy i 0,00 zl powyzej progu — zgodnie z logika listy transportow na /koszyk.** + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~25 min | +| Started | 2026-04-20 | +| Completed | 2026-04-20 | +| Tasks | 4 completed (3 auto + 1 checkpoint) | +| Files modified | 2 modified + 1 created | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: Transport z delivery_free ponizej progu pokazuje rzeczywisty koszt | Pass | Test `testTransportWithDeliveryFreeBelowThresholdShowsRealCost` + manualna weryfikacja | +| AC-2: Transport z delivery_free powyzej progu pokazuje 0,00 zl | Pass | Test `testTransportWithDeliveryFreeAboveThresholdShowsZero` + manualna weryfikacja | +| AC-3: Transport bez flagi delivery_free zawsze pokazuje koszt | Pass | Test `testTransportWithoutDeliveryFreeAlwaysShowsCost` + manualna weryfikacja | +| AC-4: Suita PHPUnit zielona, nowy test przechodzi | Pass | 834/834 OK, 2318 assertions (6 nowych testow) | + +## Accomplishments + +- Chroniona metoda `ShopBasketController::calculateTransportCostForSummary()` enkapsuluje regule progowa darmowej dostawy i jest czysto testowalna. +- Szablon `summary-view.php` pozbyty dwoch duplikatow logiki `delivery_free == 1` — uzywa teraz gotowych kluczy widokowych. +- 6 testow jednostkowych pokrywa 3 AC i 3 edge case'y (transport null, prog 0, wartosc koszyka rowna progowi). +- Pelna suita zgadza sie z docs/MEMORY.md (>800 testow, 821 -> 834 po fazie). + +## Task Commits + +Commit transition-phase jeszcze nie wykonany w tym UNIFY (patrz Deviations). + +| Task | Commit | Type | Description | +|------|--------|------|-------------| +| Task 1: Calc effective cost w kontrolerze | (pending) | fix | ShopBasketController::summaryView + calculateTransportCostForSummary | +| Task 2: summary-view.php uzywa nowych kluczy | (pending) | fix | Usuniety odwolanie do transport.delivery_free w bloku podsumowania | +| Task 3: Nowy test jednostkowy | (pending) | test | ShopBasketControllerSummaryViewTest (6 testow) | + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `autoload/front/Controllers/ShopBasketController.php` | Modified | Dodana protected method calculateTransportCostForSummary; summaryView przekazuje transport_cost_effective + free_delivery_applies | +| `templates/shop-basket/summary-view.php` | Modified | Wiersz kosztu transportu i suma koncowa uzywaja nowych kluczy zamiast transport.delivery_free | +| `tests/Unit/front/Controllers/ShopBasketControllerSummaryViewTest.php` | Created | 6 testow jednostkowych dla logiki kalkulacji kosztu | + +## Decisions Made + +| Decision | Rationale | Impact | +|----------|-----------|--------| +| Kalkulacja w kontrolerze, nie w TransportRepository | Repository juz ma `transportMethodsFront` robiace to samo, ale dla listy metod — dla pojedynczego wybranego transportu to decyzja widokowa nalezaca do kontrolera | Brak zmiany publicznego API Repository | +| protected + Reflection do testow | Zgodne z istniejacym wzorcem `ShopBasketControllerTest` (Reflection), nie rozszerza publicznego API | Test izolowany od sesji i globali | +| Boundary na prog > 0 | Jesli `settings.free_delivery = 0`, darmowa dostawa jest wylaczona (brak progu = brak regul) | Ochrona przed niezamierzonym zerowaniem kosztu w sklepach bez tej funkcji | + +## Deviations from Plan + +### Summary + +| Type | Count | Impact | +|------|-------|--------| +| Auto-fixed | 0 | — | +| Scope additions | 2 | 2 dodatkowe edge-case testy (boundary rowny prog + prog 0) | +| Deferred | 1 | Git commit transition-phase do wykonania w transition-phase lub rece | + +**Total impact:** Bez scope creepu; dodatki to defensywne testy edge-case'ow. + +### Auto-fixed Issues + +None. + +### Scope Additions + +**1. Test dla wartosci koszyka rownej progowi** +- **Found during:** Task 3 (test jednostkowy) +- **Issue:** Plan AC-2 mowi "powyzej progu", granica rowna progowi nie byla pokryta +- **Fix:** Dodany `testTransportWithDeliveryFreeAtExactThresholdShowsZero` +- **Rationale:** Stare `transportMethodsFront` uzywa `>=` — utrzymana spojnosc + +**2. Test dla settings.free_delivery = 0** +- **Found during:** Task 1 (implementacja) +- **Issue:** Sklepy bez ustawionego progu darmowej dostawy nie mogly miec zerowanych transportow; guard na > 0 wart pokrycia testem +- **Fix:** Dodany `testZeroFreeDeliveryThresholdDisablesFreeDelivery` + +### Deferred Items + +- Transition-phase git commit do uruchomienia w ramach `/paul:transition` lub recznego commita (spojne z historycznym wzorcem faz 15 i 16). + +## Issues Encountered + +| Issue | Resolution | +|-------|------------| +| `test.ps1` nie istnieje w repo (pomimo wzmianki w CLAUDE.md) | Uruchomiono phpunit.phar bezposrednio przez `C:/xampp/php/php.exe phpunit.phar -c phpunit.xml` | + +## Next Phase Readiness + +**Ready:** +- Logika kosztu transportu w checkoutu spojna miedzy /koszyk i /koszyk-podsumowanie. +- Pelna suita zielona. + +**Concerns:** +- Git commit nie wykonany automatycznie — nalezy domknac w transition-phase. +- CLAUDE.md odwoluje sie do `./test.ps1` ktorego nie ma w repo — do rozwazenia porzadkowo. + +**Blockers:** +- None. + +--- +*Phase: 17-cart-summary-transport-cost-fix, Plan: 01* +*Completed: 2026-04-20* diff --git a/autoload/front/Controllers/ShopBasketController.php b/autoload/front/Controllers/ShopBasketController.php index e26e7fa..7477b91 100644 --- a/autoload/front/Controllers/ShopBasketController.php +++ b/autoload/front/Controllers/ShopBasketController.php @@ -280,20 +280,71 @@ class ShopBasketController $client = \Shared\Helpers\Helpers::get_session( 'client' ); $orderSubmitToken = $this->createOrderSubmitToken(); + $basket = \Shared\Helpers\Helpers::get_session( 'basket' ); + $coupon = \Shared\Helpers\Helpers::get_session( 'coupon' ); + $transport = ( new \Domain\Transport\TransportRepository( $GLOBALS['mdb'] ) )->findActiveByIdCached( \Shared\Helpers\Helpers::get_session( 'basket-transport-method-id' ) ); + + $productsSummary = (float)\Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon ); + $freeDeliveryThreshold = isset( $settings['free_delivery'] ) ? (float)$settings['free_delivery'] : 0.0; + $transportCalc = $this->calculateTransportCostForSummary( $transport, $productsSummary, $freeDeliveryThreshold ); + return \Shared\Tpl\Tpl::view( 'shop-basket/summary-view', [ 'lang_id' => $lang_id, 'client' => \Shared\Helpers\Helpers::get_session( 'client' ), - 'basket' => \Shared\Helpers\Helpers::get_session( 'basket' ), - 'transport' => ( new \Domain\Transport\TransportRepository( $GLOBALS['mdb'] ) )->findActiveByIdCached( \Shared\Helpers\Helpers::get_session( 'basket-transport-method-id' ) ), + 'basket' => $basket, + 'transport' => $transport, + 'transport_cost_effective' => $transportCalc['transport_cost_effective'], + 'free_delivery_applies' => $transportCalc['free_delivery_applies'], 'payment_method' => $this->paymentMethodRepository->paymentMethodCached( (int)\Shared\Helpers\Helpers::get_session( 'basket-payment-method-id' ) ), 'addresses' => ( new \Domain\Client\ClientRepository( $GLOBALS['mdb'] ) )->clientAddresses( (int)$client['id'] ), 'settings' => $settings, - 'coupon' => \Shared\Helpers\Helpers::get_session( 'coupon' ), + 'coupon' => $coupon, 'basket_message' => \Shared\Helpers\Helpers::get_session( 'basket_message' ), 'order_submit_token' => $orderSubmitToken ] ); } + /** + * Wylicza efektywny koszt transportu dla widoku /koszyk-podsumowanie. + * Koszt spada do 0, gdy transport ma flage delivery_free=1 ORAZ wartosc koszyka + * (po kuponie) osiaga prog darmowej dostawy $freeDeliveryThreshold. + * + * @param array|null $transport Aktywny transport (lub null gdy nie wybrany) + * @param float $productsSummary Wartosc koszyka po kuponie + * @param float $freeDeliveryThreshold Prog darmowej dostawy z settings.free_delivery + * @return array{transport_cost_effective: float, free_delivery_applies: bool} + */ + protected function calculateTransportCostForSummary( $transport, $productsSummary, $freeDeliveryThreshold ) + { + if ( !is_array( $transport ) ) + { + return [ + 'transport_cost_effective' => 0.0, + 'free_delivery_applies' => false, + ]; + } + + $deliveryFree = isset( $transport['delivery_free'] ) ? (int)$transport['delivery_free'] : 0; + $cost = isset( $transport['cost'] ) ? (float)$transport['cost'] : 0.0; + + $applies = false; + if ( $deliveryFree === 1 && $freeDeliveryThreshold > 0 ) + { + $summaryNormalized = \Shared\Helpers\Helpers::normalize_decimal( $productsSummary ); + $thresholdNormalized = \Shared\Helpers\Helpers::normalize_decimal( $freeDeliveryThreshold ); + + if ( $summaryNormalized >= $thresholdNormalized ) + { + $applies = true; + } + } + + return [ + 'transport_cost_effective' => $applies ? 0.0 : $cost, + 'free_delivery_applies' => $applies, + ]; + } + public function basketSave() { $orderSubmitToken = (string)\Shared\Helpers\Helpers::get( 'order_submit_token', true ); diff --git a/templates/shop-basket/summary-view.php b/templates/shop-basket/summary-view.php index 26e373d..838c373 100644 --- a/templates/shop-basket/summary-view.php +++ b/templates/shop-basket/summary-view.php @@ -97,11 +97,11 @@
transport[ 'name_visible' ];?>: - transport[ 'delivery_free' ] == 1 ):?> + free_delivery_applies ):?> 0,00 zł - transport[ 'cost' ] );?> zł + transport_cost_effective );?> zł
@@ -111,7 +111,7 @@ $summary -= $discount; ?> - transport[ 'delivery_free' ] == 1 ? \Shared\Helpers\Helpers::decimal( $summary ) : \Shared\Helpers\Helpers::decimal( $summary + $this -> transport[ 'cost' ] );?> zł + free_delivery_applies ? \Shared\Helpers\Helpers::decimal( $summary ) : \Shared\Helpers\Helpers::decimal( $summary + $this -> transport_cost_effective );?> zł
diff --git a/tests/Unit/front/Controllers/ShopBasketControllerSummaryViewTest.php b/tests/Unit/front/Controllers/ShopBasketControllerSummaryViewTest.php new file mode 100644 index 0000000..1ce9b98 --- /dev/null +++ b/tests/Unit/front/Controllers/ShopBasketControllerSummaryViewTest.php @@ -0,0 +1,120 @@ +createMock(OrderRepository::class); + $paymentMethodRepository = $this->createMock(PaymentMethodRepository::class); + $this->controller = new ShopBasketController($orderRepository, $paymentMethodRepository); + } + + /** + * Wywoluje chroniona metode calculateTransportCostForSummary przez Reflection. + * + * @param array|null $transport + * @param float $productsSummary + * @param float $freeDeliveryThreshold + * @return array + */ + private function invokeCalc($transport, $productsSummary, $freeDeliveryThreshold): array + { + $reflection = new \ReflectionClass(ShopBasketController::class); + $method = $reflection->getMethod('calculateTransportCostForSummary'); + $method->setAccessible(true); + + return $method->invoke($this->controller, $transport, $productsSummary, $freeDeliveryThreshold); + } + + public function testTransportWithDeliveryFreeBelowThresholdShowsRealCost(): void + { + // AC-1: delivery_free=1, basket 150, threshold 300 -> cost 15.00 + $transport = [ + 'id' => 4, + 'cost' => 15.00, + 'delivery_free' => 1, + ]; + + $result = $this->invokeCalc($transport, 150.00, 300.00); + + $this->assertFalse($result['free_delivery_applies']); + $this->assertSame(15.00, $result['transport_cost_effective']); + } + + public function testTransportWithDeliveryFreeAboveThresholdShowsZero(): void + { + // AC-2: delivery_free=1, basket 350, threshold 300 -> cost 0.0, applies true + $transport = [ + 'id' => 4, + 'cost' => 15.00, + 'delivery_free' => 1, + ]; + + $result = $this->invokeCalc($transport, 350.00, 300.00); + + $this->assertTrue($result['free_delivery_applies']); + $this->assertSame(0.0, $result['transport_cost_effective']); + } + + public function testTransportWithDeliveryFreeAtExactThresholdShowsZero(): void + { + // Boundary: basket == threshold should trigger free delivery + $transport = [ + 'id' => 4, + 'cost' => 20.00, + 'delivery_free' => 1, + ]; + + $result = $this->invokeCalc($transport, 300.00, 300.00); + + $this->assertTrue($result['free_delivery_applies']); + $this->assertSame(0.0, $result['transport_cost_effective']); + } + + public function testTransportWithoutDeliveryFreeAlwaysShowsCost(): void + { + // AC-3: delivery_free=0, basket 500, threshold 300 -> cost 25.00, applies false + $transport = [ + 'id' => 5, + 'cost' => 25.00, + 'delivery_free' => 0, + ]; + + $result = $this->invokeCalc($transport, 500.00, 300.00); + + $this->assertFalse($result['free_delivery_applies']); + $this->assertSame(25.00, $result['transport_cost_effective']); + } + + public function testNullTransportReturnsZeroAndDoesNotApply(): void + { + // Scenario: no transport selected yet (findActiveByIdCached zwrocil null) + $result = $this->invokeCalc(null, 500.00, 300.00); + + $this->assertFalse($result['free_delivery_applies']); + $this->assertSame(0.0, $result['transport_cost_effective']); + } + + public function testZeroFreeDeliveryThresholdDisablesFreeDelivery(): void + { + // Ochrona: jesli settings.free_delivery = 0, darmowa dostawa nie dziala nigdy + $transport = [ + 'id' => 4, + 'cost' => 15.00, + 'delivery_free' => 1, + ]; + + $result = $this->invokeCalc($transport, 9999.00, 0.00); + + $this->assertFalse($result['free_delivery_applies']); + $this->assertSame(15.00, $result['transport_cost_effective']); + } +}