From 9de4afec9a8841b4acbd9bc5ee29592ede19e134 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Sun, 22 Feb 2026 15:26:51 +0100 Subject: [PATCH] ver. 0.304: Configurable payment method order amount limits Replace hardcoded PayPo condition (id=6, 40-1000 PLN) with generic min/max order amount columns on pp_shop_payment_methods. Admin form fields added, frontend basket checkout filters dynamically. Cache invalidation on save. 4 new tests (734 total, 2080 assertions). Co-Authored-By: Claude Opus 4.6 --- AGENTS.md | 5 +- CLAUDE.md | 4 +- .../PaymentMethod/PaymentMethodRepository.php | 29 +++++- .../ShopPaymentMethodController.php | 12 +++ docs/CHANGELOG.md | 11 +++ docs/DATABASE_STRUCTURE.md | 4 + docs/TESTING.md | 4 +- migrations/0.304.sql | 2 + .../shop-basket/basket-payments-methods.php | 9 +- .../PaymentMethodRepositoryTest.php | 98 +++++++++++++++++++ 10 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 migrations/0.304.sql diff --git a/AGENTS.md b/AGENTS.md index f079afe..addcb36 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,10 @@ Gdy użytkownik napisze `KONIEC PRACY`, wykonaj kolejno: - `docs/FORM_EDIT_SYSTEM.md` - `docs/CHANGELOG.md` - `docs/TESTING.md` -3. Przygotowanie aktualizacji zgodnie z plikiem docs/UPDATE_INSTRUCTIONS.md (ZIP, plik z usuwanymi plikami, plik SQL jeśli wymagany). +3. Migracje SQL (jeśli były zmiany w bazie danych): + - Plik: `migrations/{version}.sql` (np. `migrations/0.304.sql`) + - **NIE** w `updates/` — build script sam wczyta z `migrations/` + - Sprawdź czy plik istnieje i jest poprawnie nazwany przed commitem 4. Commit. 5. Push. diff --git a/CLAUDE.md b/CLAUDE.md index bfc2ce3..f52df71 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ composer test PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`. -Current suite: **730 tests, 2066 assertions**. +Current suite: **734 tests, 2080 assertions**. ### Creating Updates See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packages in `updates/0.XX/`. Never include `*.md` files, `updates/changelog.php`, or root `.htaccess` in update ZIPs. @@ -208,7 +208,7 @@ $controller = new \admin\Controllers\ExampleController($repo); When user says **"KONIEC PRACY"**, execute in order: 1. Run tests 2. Update documentation if needed: `docs/DATABASE_STRUCTURE.md`, `docs/PROJECT_STRUCTURE.md`, `docs/FORM_EDIT_SYSTEM.md`, `docs/CHANGELOG.md`, `docs/TESTING.md` -3. Prepare update package per `docs/UPDATE_INSTRUCTIONS.md` +3. SQL migrations (if DB changes): place in `migrations/{version}.sql` (e.g. `migrations/0.304.sql`). **NOT** in `updates/` — build script reads from `migrations/` automatically 4. Commit 5. Push diff --git a/autoload/Domain/PaymentMethod/PaymentMethodRepository.php b/autoload/Domain/PaymentMethod/PaymentMethodRepository.php index 3196917..d0fee49 100644 --- a/autoload/Domain/PaymentMethod/PaymentMethodRepository.php +++ b/autoload/Domain/PaymentMethod/PaymentMethodRepository.php @@ -120,10 +120,16 @@ class PaymentMethodRepository 'description' => trim((string)($data['description'] ?? '')), 'status' => $this->toSwitchValue($data['status'] ?? 0), 'apilo_payment_type_id' => $this->normalizeApiloPaymentTypeId($data['apilo_payment_type_id'] ?? null), + 'min_order_amount' => $this->normalizeDecimalOrNull($data['min_order_amount'] ?? null), + 'max_order_amount' => $this->normalizeDecimalOrNull($data['max_order_amount'] ?? null), ]; $this->db->update('pp_shop_payment_methods', $row, ['id' => $paymentMethodId]); + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheHandler->deletePattern('payment_method*'); + $cacheHandler->deletePattern('payment_methods*'); + return $paymentMethodId; } @@ -232,7 +238,9 @@ class PaymentMethodRepository spm.name, spm.description, spm.status, - spm.apilo_payment_type_id + spm.apilo_payment_type_id, + spm.min_order_amount, + spm.max_order_amount FROM pp_shop_payment_methods AS spm INNER JOIN pp_shop_transport_payment_methods AS stpm ON stpm.id_payment_method = spm.id @@ -325,6 +333,8 @@ class PaymentMethodRepository $row['description'] = (string)($row['description'] ?? ''); $row['status'] = $this->toSwitchValue($row['status'] ?? 0); $row['apilo_payment_type_id'] = $this->normalizeApiloPaymentTypeId($row['apilo_payment_type_id'] ?? null); + $row['min_order_amount'] = $this->normalizeDecimalOrNull($row['min_order_amount'] ?? null); + $row['max_order_amount'] = $this->normalizeDecimalOrNull($row['max_order_amount'] ?? null); return $row; } @@ -350,6 +360,23 @@ class PaymentMethodRepository return $text; } + /** + * @return float|null + */ + private function normalizeDecimalOrNull($value) + { + if ($value === null || $value === false) { + return null; + } + + $text = trim((string)$value); + if ($text === '') { + return null; + } + + return (float)$text; + } + private function toSwitchValue($value): int { if (is_bool($value)) { diff --git a/autoload/admin/Controllers/ShopPaymentMethodController.php b/autoload/admin/Controllers/ShopPaymentMethodController.php index 4255acd..6ae73dc 100644 --- a/autoload/admin/Controllers/ShopPaymentMethodController.php +++ b/autoload/admin/Controllers/ShopPaymentMethodController.php @@ -182,6 +182,8 @@ class ShopPaymentMethodController 'description' => (string)($paymentMethod['description'] ?? ''), 'status' => (int)($paymentMethod['status'] ?? 0), 'apilo_payment_type_id' => $paymentMethod['apilo_payment_type_id'] ?? '', + 'min_order_amount' => $paymentMethod['min_order_amount'] ?? '', + 'max_order_amount' => $paymentMethod['max_order_amount'] ?? '', ]; $fields = [ @@ -203,6 +205,16 @@ class ShopPaymentMethodController 'tab' => 'settings', 'rows' => 5, ]), + FormField::number('min_order_amount', [ + 'label' => 'Min. kwota zamowienia (PLN)', + 'tab' => 'settings', + 'step' => 0.01, + ]), + FormField::number('max_order_amount', [ + 'label' => 'Maks. kwota zamowienia (PLN)', + 'tab' => 'settings', + 'step' => 0.01, + ]), FormField::select('apilo_payment_type_id', [ 'label' => 'Typ platnosci Apilo', 'tab' => 'settings', diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 71b2953..b46472d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,17 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. --- +## ver. 0.304 (2026-02-22) - Konfigurowalne limity kwotowe metod platnosci + +- **NEW**: Kolumny `min_order_amount` i `max_order_amount` w `pp_shop_payment_methods` — konfigurowalne limity kwotowe per metoda platnosci +- **NEW**: Pola min/max kwoty zamowienia w formularzu edycji metody platnosci (admin) +- **FIX**: Zastapiono hardcoded warunek PayPo (id=6, 40-1000 PLN) generycznym filtrowaniem na froncie (basket checkout) +- **NEW**: Cache invalidation po zapisie metody platnosci +- **NEW**: 4 nowe testy jednostkowe (734 total, 2080 assertions) +- **MIGRATION**: `migrations/0.304.sql` — ALTER TABLE pp_shop_payment_methods ADD min/max_order_amount + +--- + ## ver. 0.303 (2026-02-22) - Fix: wyswietlanie atrybutow produktu na froncie + podglad produktu w adminie - **FIX**: Naprawiono wyswietlanie atrybutow produktu na froncie — gdy dwa atrybuty mialy te sama wartosc kolejnosci (`o`), jeden nadpisywal drugi (kolizja kluczy tablicy). Teraz atrybuty sortowane przez `usort()` z unikalnymi kluczami sekwencyjnymi. diff --git a/docs/DATABASE_STRUCTURE.md b/docs/DATABASE_STRUCTURE.md index 5e22dca..ada59ba 100644 --- a/docs/DATABASE_STRUCTURE.md +++ b/docs/DATABASE_STRUCTURE.md @@ -508,12 +508,16 @@ Metody platnosci sklepu (modul `/admin/shop_payment_method`). | description | Opis metody platnosci (wyswietlany m.in. w checkout) | | status | Status: 1 = aktywna, 0 = nieaktywna | | apilo_payment_type_id | ID typu platnosci Apilo (NULL gdy brak mapowania) | +| min_order_amount | Minimalna kwota zamowienia (DECIMAL(10,2), NULL = brak limitu) | +| max_order_amount | Maksymalna kwota zamowienia (DECIMAL(10,2), NULL = brak limitu) | | sellasist_payment_type_id | DEPRECATED (integracja Sellasist usunieta w ver. 0.263) | **Uzywane w:** `Domain\PaymentMethod\PaymentMethodRepository`, `admin\Controllers\ShopPaymentMethodController`, `front\factory\ShopPaymentMethod`, `shop\PaymentMethod`, `admin\controls\ShopTransport`, `cron.php` **Aktualizacja 2026-02-14 (ver. 0.268):** modul `/admin/shop_payment_method` korzysta z `Domain\PaymentMethod\PaymentMethodRepository` przez `admin\Controllers\ShopPaymentMethodController`. Usunieto legacy klasy `admin\controls\ShopPaymentMethod`, `admin\factory\ShopPaymentMethod`, `admin\view\ShopPaymentMethod` oraz widok `admin/templates/shop-payment-method/view-list.php`. +**Aktualizacja 2026-02-22 (ver. 0.304):** dodano kolumny `min_order_amount` i `max_order_amount` — konfigurowalne limity kwotowe metod platnosci. Zastapiono hardcoded warunek PayPo (id=6, 40-1000 PLN) generycznym filtrowaniem na froncie. + ## pp_shop_transports Rodzaje transportu sklepu (modul `/admin/shop_transport`). diff --git a/docs/TESTING.md b/docs/TESTING.md index 1ba285c..bb97968 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -23,10 +23,10 @@ composer test # standard ## Aktualny stan ```text -OK (730 tests, 2066 assertions) +OK (734 tests, 2080 assertions) ``` -Zweryfikowano: 2026-02-21 (ver. 0.300) +Zweryfikowano: 2026-02-22 (ver. 0.304) ## Konfiguracja diff --git a/migrations/0.304.sql b/migrations/0.304.sql new file mode 100644 index 0000000..ccdfec1 --- /dev/null +++ b/migrations/0.304.sql @@ -0,0 +1,2 @@ +ALTER TABLE pp_shop_payment_methods ADD COLUMN min_order_amount DECIMAL(10,2) DEFAULT NULL; +ALTER TABLE pp_shop_payment_methods ADD COLUMN max_order_amount DECIMAL(10,2) DEFAULT NULL; diff --git a/templates/shop-basket/basket-payments-methods.php b/templates/shop-basket/basket-payments-methods.php index 4568a64..2d27be5 100644 --- a/templates/shop-basket/basket-payments-methods.php +++ b/templates/shop-basket/basket-payments-methods.php @@ -13,7 +13,14 @@ $basket_summary = \Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon ) + $transport_cost; ?> payment_methods ) ): foreach ( $this -> payment_methods as $payment_method ):?> - = 40 and $basket_summary <= 1000 ):?> + 0 && $basket_summary < $min) $show = false; + if ($max !== null && $max > 0 && $basket_summary > $max) $show = false; + ?> +
assertSame('test', $updateRow['description']); $this->assertSame(1, $updateRow['status']); $this->assertSame(22, $updateRow['apilo_payment_type_id']); + $this->assertNull($updateRow['min_order_amount']); + $this->assertNull($updateRow['max_order_amount']); $this->assertSame(['id' => 3], $updateWhere); } @@ -113,6 +115,102 @@ class PaymentMethodRepositoryTest extends TestCase $this->assertNull($repository->save(0, ['status' => 1])); } + public function testSavePersistsMinMaxOrderAmount(): void + { + $mockDb = $this->createMock(\medoo::class); + $updateRow = null; + + $mockDb->expects($this->once()) + ->method('update') + ->willReturnCallback(function ($table, $row) use (&$updateRow) { + $updateRow = $row; + return true; + }); + + $repository = new PaymentMethodRepository($mockDb); + $repository->save(5, [ + 'description' => 'test', + 'status' => 1, + 'apilo_payment_type_id' => '', + 'min_order_amount' => '40.00', + 'max_order_amount' => '1000.00', + ]); + + $this->assertSame(40.0, $updateRow['min_order_amount']); + $this->assertSame(1000.0, $updateRow['max_order_amount']); + } + + public function testSaveConvertsEmptyMinMaxToNull(): void + { + $mockDb = $this->createMock(\medoo::class); + $updateRow = null; + + $mockDb->expects($this->once()) + ->method('update') + ->willReturnCallback(function ($table, $row) use (&$updateRow) { + $updateRow = $row; + return true; + }); + + $repository = new PaymentMethodRepository($mockDb); + $repository->save(6, [ + 'description' => 'test', + 'status' => 1, + 'apilo_payment_type_id' => '', + 'min_order_amount' => '', + 'max_order_amount' => '', + ]); + + $this->assertNull($updateRow['min_order_amount']); + $this->assertNull($updateRow['max_order_amount']); + } + + public function testFindNormalizesMinMaxOrderAmount(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_shop_payment_methods', '*', ['id' => 6]) + ->willReturn([ + 'id' => '6', + 'name' => 'PayPo', + 'description' => '', + 'status' => '1', + 'apilo_payment_type_id' => null, + 'min_order_amount' => '40.00', + 'max_order_amount' => '1000.00', + ]); + + $repository = new PaymentMethodRepository($mockDb); + $result = $repository->find(6); + + $this->assertSame(40.0, $result['min_order_amount']); + $this->assertSame(1000.0, $result['max_order_amount']); + } + + public function testFindNormalizesNullMinMaxOrderAmount(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_shop_payment_methods', '*', ['id' => 7]) + ->willReturn([ + 'id' => '7', + 'name' => 'Przelew', + 'description' => '', + 'status' => '1', + 'apilo_payment_type_id' => null, + 'min_order_amount' => null, + 'max_order_amount' => null, + ]); + + $repository = new PaymentMethodRepository($mockDb); + $result = $repository->find(7); + + $this->assertNull($result['min_order_amount']); + $this->assertNull($result['max_order_amount']); + } + public function testListForAdminWhitelistsSortAndDirection(): void { $mockDb = $this->createMock(\medoo::class);