Compare commits

...

4 Commits

Author SHA1 Message Date
562495f120 build: update package v0.304
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 15:28:19 +01:00
9de4afec9a 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 <noreply@anthropic.com>
2026-02-22 15:26:51 +01:00
3a3c2adb47 build: update package v0.303
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 15:05:19 +01:00
db7c881d36 ver. 0.303: Fix attribute display collision + product preview button
Fix: product attributes with the same sort order value were overwriting
each other in getProductAttributes(), causing only one attribute to
display on the frontend. Now uses usort() with sequential keys.

New: Preview button in product edit form opens product page in new tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 15:04:51 +01:00
19 changed files with 248 additions and 53 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -61,6 +61,10 @@ $_SESSION['can_use_rfm'] = true;
<a href="<?= htmlspecialchars($action->url) ?>" class="btn btn-dark btn-sm" id="g-edit-cancel">
<i class="fa fa-reply mr5"></i>Wstecz
</a>
<?php elseif ($action->name === 'preview'): ?>
<a href="<?= htmlspecialchars($action->url) ?>" class="btn btn-info btn-sm" target="_blank">
<i class="fa fa-eye mr5"></i><?= htmlspecialchars($action->label) ?>
</a>
<?php else: ?>
<a href="<?= htmlspecialchars($action->url) ?>" class="btn <?= htmlspecialchars($action->cssClass) ?> btn-sm">
<?= htmlspecialchars($action->label) ?>

View File

@@ -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)) {

View File

@@ -3359,12 +3359,18 @@ class ProductRepository
$attributes = \Shared\Helpers\Helpers::removeDuplicates($attributes, 'id');
$sorted = [];
$toSort = [];
foreach ($attributes as $key => $val) {
$row = [];
$row['id'] = $key;
$row['values'] = $val;
$sorted[$attrRepo->getAttributeOrder((int) $key)] = $row;
$toSort[] = ['order' => (int) $attrRepo->getAttributeOrder((int) $key), 'data' => $row];
}
usort($toSort, function ($a, $b) { return $a['order'] - $b['order']; });
$sorted = [];
foreach ($toSort as $i => $item) {
$sorted[$i + 1] = $item['data'];
}
return $sorted;

View File

@@ -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',

View File

@@ -547,6 +547,11 @@ class ShopProductController
FormAction::cancel( $backUrl ),
];
if ( $productId > 0 ) {
$previewUrl = $this->repository->getProductUrl( $productId );
$actions[] = FormAction::preview( $previewUrl );
}
return new FormEditViewModel(
'product-edit',
$title,

View File

@@ -56,6 +56,22 @@ class FormAction
);
}
/**
* Predefiniowana akcja Podgląd (otwiera w nowej karcie)
*/
public static function preview(string $url, string $label = 'Podgląd'): self
{
return new self(
'preview',
$label,
$url,
null,
'btn btn-info',
'link',
['target' => '_blank']
);
}
/**
* Predefiniowana akcja Anuluj
*/

View File

@@ -4,6 +4,25 @@ 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.
- **NEW**: Przycisk "Podglad" w formularzu edycji produktu — otwiera strone produktu w nowej karcie
- **NEW**: `FormAction::preview()` — nowy typ akcji formularza z `target="_blank"`
---
## ver. 0.302 (2026-02-22) - REST API: warianty produktow, atrybuty, filtrowanie
- **NEW**: API wariantow produktow — CRUD: `variants`, `create_variant`, `update_variant`, `delete_variant`

View File

@@ -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`).

View File

@@ -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

View File

@@ -69,45 +69,5 @@ Build script automatycznie je wczyta i umieści w manifeście + legacy `_sql.txt
### .updateignore
Plik w katalogu głównym projektu, wzorce plików wykluczonych z paczek (jak `.gitignore`).
---
## Stary sposób (do v0.300) — ręczne pakowanie
### Struktura aktualizacji
Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesiątki wersji.
#### Pliki aktualizacji:
- `ver_X.XXX.zip` - paczka ZIP ze zmienionymi plikami (BEZ folderu wersji, bezpośrednio struktura katalogów)
- `ver_X.XXX_sql.txt` - opcjonalny plik z zapytaniami SQL (jeśli wymagane zmiany w bazie)
- `ver_X.XXX_files.txt` - opcjonalny plik z listą plików do **USUNIĘCIA** przy aktualizacji (format: `F: ../sciezka/do/pliku.php`)
- `changelog.php` - historia zmian
- `versions.php` - konfiguracja wersji (zmienna `$current_ver`)
#### Zasada pakowania plików
- Do paczek aktualizacji **nie dodajemy plików `*.md`** (dokumentacja jest tylko wewnętrzna/deweloperska).
- Do paczek aktualizacji **nie dodajemy `updates/changelog.php`** (to plik serwisowy po stronie repozytorium aktualizacji, nie runtime klienta).
- Do paczek aktualizacji **nie dodajemy głównego `.htaccess` z katalogu projektu** (ten plik wdrażamy osobno, poza ZIP aktualizacji).
### Procedura ręczna
1. Określ numer wersji
2. Utwórz folder tymczasowy: `mkdir -p temp/temp_XXX/sciezka/do/pliku`
3. Skopiuj zmienione pliki do folderu tymczasowego
4. Utwórz ZIP z zawartości folderu (nie z samego folderu!)
5. Usuń folder tymczasowy
6. Zaktualizuj `changelog.php` i `versions.php`
7. (Opcjonalnie) Utwórz `_sql.txt` i `_files.txt`
**WAŻNE:** W archiwum ZIP NIE powinno być folderu z nazwą wersji. Struktura ZIP zaczyna się bezpośrednio od katalogów projektu (admin/, autoload/, itp.).
## Status bieżącej aktualizacji (ver. 0.302)
- Wersja udostępniona: `0.302` (data: 2026-02-22).
- Pliki publikacyjne:
- `updates/0.30/ver_0.302.zip`
- Pliki metadanych aktualizacji:
- `updates/changelog.php`
- `updates/versions.php` (`$current_ver = 302`)
- Weryfikacja testów przed publikacją:
- `OK (730 tests, 2066 assertions)`
### INFO
pamiętaj że push czasem zwraca błąd autoryzacji, wtedy spróbuj ponownie

2
migrations/0.304.sql Normal file
View File

@@ -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;

View File

@@ -13,7 +13,14 @@
$basket_summary = \Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon ) + $transport_cost;
?>
<? if ( is_array( $this -> payment_methods ) ): foreach ( $this -> payment_methods as $payment_method ):?>
<? if ( $payment_method['id'] != 6 or $payment_method['id'] == 6 and $basket_summary >= 40 and $basket_summary <= 1000 ):?>
<?
$min = isset($payment_method['min_order_amount']) ? (float)$payment_method['min_order_amount'] : null;
$max = isset($payment_method['max_order_amount']) ? (float)$payment_method['max_order_amount'] : null;
$show = true;
if ($min !== null && $min > 0 && $basket_summary < $min) $show = false;
if ($max !== null && $max > 0 && $basket_summary > $max) $show = false;
?>
<? if ( $show ):?>
<div class="options">
<div class="check">
<input type="radio" class="icheck" name="payment_method" value="<?= $payment_method['id'];?>"

View File

@@ -78,6 +78,8 @@ class PaymentMethodRepositoryTest extends TestCase
$this->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);

BIN
updates/0.30/ver_0.303.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,26 @@
{
"changelog": "FIX - naprawiono wyswietlanie atrybutow produktu na froncie (kolizja kolejnosci), NEW - przycisk Podglad w edycji produktu",
"version": "0.303",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"admin/templates/components/form-edit.php",
"autoload/Domain/Product/ProductRepository.php",
"autoload/admin/Controllers/ShopProductController.php",
"autoload/admin/ViewModels/Forms/FormAction.php"
]
},
"checksum_zip": "sha256:6d8cc0c3419c50345d9c4fa1f526a09004194cbf7f76302f3cfe7afe64b00545",
"sql": [
],
"date": "2026-02-22",
"directories_deleted": [
]
}

View File

@@ -1,4 +1,10 @@
<b>ver. 0.302 - 22.02.2026</b><br />
<b>ver. 0.304 - 22.02.2026</b><br />
NEW - konfigurowalne limity kwotowe metod platnosci (min/max kwota zamowienia), zastapienie hardcoded warunku PayPo
<hr>
<b>ver. 0.303 - 22.02.2026</b><br />
FIX - naprawiono wyswietlanie atrybutow produktu na froncie (kolizja kolejnosci), NEW - przycisk Podglad w edycji produktu
<hr>
<b>ver. 0.302 - 22.02.2026</b><br />
NEW - REST API wariantów produktów (CRUD), słownik atrybutów, filtrowanie po atrybutach, wzbogacone atrybuty z tłumaczeniami
<hr>
<b>ver. 0.301 - 22.02.2026</b><br />

View File

@@ -1,5 +1,5 @@
<?
$current_ver = 302;
$current_ver = 304;
for ($i = 1; $i <= $current_ver; $i++)
{