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.
+
+
+
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 @@
= $this -> transport[ 'name_visible' ];?>:
- if ( $this -> transport[ 'delivery_free' ] == 1 ):?>
+ if ( $this -> free_delivery_applies ):?>
0,00 zł
else:?>
- = \Shared\Helpers\Helpers::decimal( $this -> transport[ 'cost' ] );?> zł
+ = \Shared\Helpers\Helpers::decimal( $this -> transport_cost_effective );?> zł
endif;?>
@@ -111,7 +111,7 @@
$summary -= $discount;
?>
- = $this -> transport[ 'delivery_free' ] == 1 ? \Shared\Helpers\Helpers::decimal( $summary ) : \Shared\Helpers\Helpers::decimal( $summary + $this -> transport[ 'cost' ] );?> zł
+ = $this -> 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']);
+ }
+}