Refactor code structure for improved readability and maintainability
This commit is contained in:
File diff suppressed because one or more lines are too long
107
DATABASE_STRUCTURE.md
Normal file
107
DATABASE_STRUCTURE.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Struktura bazy danych shopPRO
|
||||
|
||||
Plik aktualizowany na bieżąco przy zmianach w kodzie.
|
||||
ORM: Medoo (`$mdb`), prefix tabel: `pp_`
|
||||
|
||||
## pp_shop_products
|
||||
Główna tabela produktów.
|
||||
|
||||
| Kolumna | Opis |
|
||||
|---------|------|
|
||||
| id | PK |
|
||||
| parent_id | FK do produktu nadrzędnego (kombinacje) - NULL dla produktów głównych |
|
||||
| price_brutto | Cena brutto |
|
||||
| price_brutto_promo | Cena promocyjna brutto |
|
||||
| quantity | Stan magazynowy |
|
||||
| status | Status: 1 = aktywny, 0 = nieaktywny |
|
||||
| archive | Archiwum: 1 = zarchiwizowany, 0 = aktywny |
|
||||
| promoted | Czy promowany |
|
||||
| vat | Stawka VAT |
|
||||
| ean | Kod EAN |
|
||||
| sku | Kod SKU |
|
||||
| baselinker_product_name | Nazwa produktu w Baselinker |
|
||||
| apilo_product_name | Nazwa produktu w Apilo |
|
||||
|
||||
**Używane w:** `Domain\Product\ProductRepository`, `admin\factory\ShopProduct`
|
||||
|
||||
## pp_shop_products_langs
|
||||
Tłumaczenia produktów (per język).
|
||||
|
||||
| Kolumna | Opis |
|
||||
|---------|------|
|
||||
| id | PK |
|
||||
| product_id | FK do pp_shop_products |
|
||||
| lang_id | ID języka (np. 'pl') |
|
||||
| name | Nazwa produktu |
|
||||
|
||||
**Używane w:** `Domain\Product\ProductRepository::getName()`
|
||||
|
||||
## pp_shop_products_images
|
||||
Zdjęcia produktów.
|
||||
|
||||
| Kolumna | Opis |
|
||||
|---------|------|
|
||||
| id | PK |
|
||||
| product_id | FK do pp_shop_products |
|
||||
| src | Ścieżka do pliku |
|
||||
| alt | Tekst alternatywny |
|
||||
|
||||
## pp_shop_products_categories
|
||||
Przypisanie produktów do kategorii.
|
||||
|
||||
| Kolumna | Opis |
|
||||
|---------|------|
|
||||
| product_id | FK do pp_shop_products |
|
||||
|
||||
**Używane w:** `admin\factory\ShopProduct::product_delete()`
|
||||
|
||||
## pp_banners
|
||||
Banery.
|
||||
|
||||
| Kolumna | Opis |
|
||||
|---------|------|
|
||||
| id | PK |
|
||||
| name | Nazwa banera |
|
||||
| status | 0/1 |
|
||||
| date_start | Data rozpoczęcia |
|
||||
| date_end | Data zakończenia |
|
||||
| home_page | Czy na stronie głównej 0/1 |
|
||||
|
||||
**Używane w:** `Domain\Banner\BannerRepository`
|
||||
|
||||
## pp_banners_langs
|
||||
Tłumaczenia banerów.
|
||||
|
||||
| Kolumna | Opis |
|
||||
|---------|------|
|
||||
| id | PK |
|
||||
| id_banner | FK do pp_banners |
|
||||
| id_lang | ID języka |
|
||||
| src | Ścieżka do grafiki |
|
||||
| url | URL docelowy |
|
||||
| html | Kod HTML |
|
||||
| text | Tekst |
|
||||
|
||||
**Używane w:** `Domain\Banner\BannerRepository`
|
||||
|
||||
## pp_articles
|
||||
Artykuły.
|
||||
|
||||
| Kolumna | Opis |
|
||||
|---------|------|
|
||||
| id | PK |
|
||||
| status | -1 = archiwum, 0 = nieaktywny, 1 = aktywny |
|
||||
|
||||
**Używane w:** `admin\controls\ArticlesArchive`
|
||||
|
||||
## pp_articles_pages
|
||||
Strony artykułów.
|
||||
|
||||
## pp_articles_langs
|
||||
Tłumaczenia artykułów.
|
||||
|
||||
## pp_articles_images
|
||||
Zdjęcia artykułów.
|
||||
|
||||
## pp_articles_files
|
||||
Pliki artykułów.
|
||||
@@ -185,7 +185,7 @@ $product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash)
|
||||
autoload/
|
||||
├── Domain/ # Nowa warstwa biznesowa (namespace \Domain\)
|
||||
│ ├── Product/
|
||||
│ │ └── ProductRepository.php # getQuantity, getPrice, getName, find, updateQuantity
|
||||
│ │ └── ProductRepository.php # getQuantity, getPrice, getName, find, updateQuantity, archive, unarchive
|
||||
│ ├── Banner/
|
||||
│ │ └── BannerRepository.php # find, delete, save
|
||||
│ ├── Settings/
|
||||
@@ -194,8 +194,9 @@ autoload/
|
||||
│ └── CacheRepository.php # clearCache (dirs + Redis)
|
||||
├── admin/
|
||||
│ ├── Controllers/ # Nowe kontrolery (namespace \admin\Controllers\)
|
||||
│ │ ├── BannerController.php # DI, instancyjny
|
||||
│ │ └── SettingsController.php # DI, instancyjny (clearCache, save, view)
|
||||
│ │ ├── BannerController.php # DI, instancyjny
|
||||
│ │ ├── SettingsController.php # DI, instancyjny (clearCache, save, view)
|
||||
│ │ └── ProductArchiveController.php # DI, instancyjny (list, unarchive)
|
||||
│ ├── class.Site.php # Router: nowy kontroler → fallback stary
|
||||
│ ├── controls/ # Stare kontrolery (niezależny fallback)
|
||||
│ ├── factory/ # Stare helpery (niezależny fallback)
|
||||
@@ -240,13 +241,25 @@ tests/
|
||||
│ │ ├── Settings/SettingsRepositoryTest.php # 3 testy
|
||||
│ │ └── Cache/CacheRepositoryTest.php # 4 testy
|
||||
│ └── admin/
|
||||
│ └── Controllers/SettingsControllerTest.php # 7 testów
|
||||
│ └── Controllers/
|
||||
│ ├── SettingsControllerTest.php # 7 testów
|
||||
│ └── ProductArchiveControllerTest.php # 6 testów
|
||||
└── Integration/
|
||||
```
|
||||
**Łącznie: 29 testów, 60 asercji**
|
||||
**Łącznie: 39 testów, 73 asercji**
|
||||
|
||||
## Ostatnie modyfikacje
|
||||
|
||||
### 2026-02-06: Migracja ProductArchive (ver. 0.241)
|
||||
- **NOWE:** `admin\Controllers\ProductArchiveController` - kontroler archiwum produktów z DI
|
||||
- **NOWE:** `ProductRepository::archive()`, `unarchive()` - operacje archiwizacji w repozytorium
|
||||
- **RENAME:** `admin/templates/archive/` → `admin/templates/product_archive/`
|
||||
- **FIX:** SQL w `ajax_products_list_archive()` - puste wyszukiwanie generowało `name|ean|sku LIKE '%%'` (NULL bitwise OR filtrował wyniki)
|
||||
- **FIX:** Brakujący `archive = 1` w branchu bez wyszukiwania
|
||||
- **CLEANUP:** Usunięto zbędny JS z szablonu archiwum (apilo, baselinker, duplikowanie, edycja cen)
|
||||
- Stary kontroler `admin\controls\Archive` zachowany jako fallback
|
||||
- Testy: 39 testów, 73 asercji (+10 nowych)
|
||||
|
||||
### 2026-02-05: Migracja Settings + Cache (ver. 0.240)
|
||||
- **NOWE:** `Domain\Settings\SettingsRepository` - repozytorium ustawień (fasada → factory)
|
||||
- **NOWE:** `Domain\Cache\CacheRepository` - repozytorium cache (dirs + Redis)
|
||||
|
||||
@@ -13,7 +13,7 @@ Stopniowe przeniesienie logiki biznesowej do architektury warstwowej:
|
||||
autoload/
|
||||
├── Domain/ # Logika biznesowa (CORE) - namespace \Domain\
|
||||
│ ├── Product/
|
||||
│ │ ├── ProductRepository.php # ✅ Zmigrowane (getQuantity, getPrice, getName, find, updateQuantity)
|
||||
│ │ ├── ProductRepository.php # ✅ Zmigrowane (getQuantity, getPrice, getName, find, updateQuantity, archive, unarchive)
|
||||
│ │ ├── ProductService.php # Logika biznesowa (przyszłość)
|
||||
│ │ └── ProductCacheService.php # Cache produktu (przyszłość)
|
||||
│ ├── Banner/
|
||||
@@ -155,6 +155,13 @@ grep -r "Product::getQuantity" .
|
||||
- Testy: ✅ 2 nowe testy (nazwa znaleziona, nie znaleziona)
|
||||
- Użycie: brak aktywnych wywołań (przygotowane na przyszłość)
|
||||
- Aktualizacja: ver. 0.239
|
||||
- ✅ archive() / unarchive() - **ZMIGROWANE** (2026-02-06) 🎉
|
||||
- Nowe metody: `Domain\Product\ProductRepository::archive()`, `unarchive()`
|
||||
- Nowy kontroler: `admin\Controllers\ProductArchiveController` (DI, instancyjny)
|
||||
- Szablony: `admin/templates/product_archive/` (rename z `archive/`)
|
||||
- Testy: ✅ 4 nowe testy repozytorium + 6 testów kontrolera
|
||||
- FIX: SQL bug w `ajax_products_list_archive()` (puste wyszukiwanie + brak `archive = 1`)
|
||||
- Aktualizacja: ver. 0.241
|
||||
- [ ] is_product_on_promotion() - NASTĘPNA 👉
|
||||
|
||||
- **Banner** (DEMO pełnej migracji kontrolera)
|
||||
@@ -200,15 +207,17 @@ composer require --dev phpunit/phpunit
|
||||
tests/
|
||||
├── Unit/
|
||||
│ ├── Domain/
|
||||
│ │ ├── Product/ProductRepositoryTest.php # 11 testów
|
||||
│ │ ├── Product/ProductRepositoryTest.php # 15 testów
|
||||
│ │ ├── Banner/BannerRepositoryTest.php # 4 testy
|
||||
│ │ ├── Settings/SettingsRepositoryTest.php # 3 testy
|
||||
│ │ └── Cache/CacheRepositoryTest.php # 4 testy
|
||||
│ └── admin/
|
||||
│ └── Controllers/SettingsControllerTest.php # 7 testów
|
||||
│ └── Controllers/
|
||||
│ ├── SettingsControllerTest.php # 7 testów
|
||||
│ └── ProductArchiveControllerTest.php # 6 testów
|
||||
└── Integration/
|
||||
```
|
||||
**Łącznie: 29 testów, 60 asercji**
|
||||
**Łącznie: 39 testów, 73 asercji**
|
||||
|
||||
### Przykład testu
|
||||
```php
|
||||
@@ -283,12 +292,14 @@ vendor/bin/phpstan analyse autoload/Domain
|
||||
- ✅ getQuantity (ver. 0.238)
|
||||
- ✅ getPrice (ver. 0.239)
|
||||
- ✅ getName (ver. 0.239)
|
||||
- ✅ archive / unarchive (ver. 0.241)
|
||||
- [ ] is_product_on_promotion - NASTĘPNA 👉
|
||||
- [ ] getFromCache
|
||||
- [ ] getProductImg
|
||||
3. **Banner** ✅ (pełna migracja kontrolera, ver. 0.239)
|
||||
4. **Settings** ✅ (migracja kontrolera - krok pośredni, ver. 0.240)
|
||||
5. **Order**
|
||||
5. **ProductArchive** ✅ (migracja kontrolera + cleanup szablonów, ver. 0.241)
|
||||
6. **Order**
|
||||
5. **Category**
|
||||
6. **ShopAttribute**
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią
|
||||
## Procedura tworzenia nowej aktualizacji
|
||||
|
||||
### 1. Określ numer wersji
|
||||
Sprawdź ostatnią wersję w `updates/0.20/` i zwiększ o 1.
|
||||
Sprawdź ostatnią wersję w `temp/` i zwiększ o 1.
|
||||
|
||||
### 2. Utwórz folder tymczasowy ze strukturą
|
||||
### 2. Utwórz folder tymczasowy ze strukturą w katalogu temp
|
||||
```bash
|
||||
mkdir -p updates/0.20/temp_XXX/sciezka/do/pliku
|
||||
mkdir -p temp/temp_XXX/sciezka/do/pliku
|
||||
```
|
||||
|
||||
**WAŻNE:** W archiwum ZIP NIE powinno być folderu z nazwą wersji (np. ver_0.234/).
|
||||
@@ -26,18 +26,18 @@ Struktura ZIP powinna zaczynać się bezpośrednio od katalogów projektu (admin
|
||||
|
||||
### 3. Skopiuj zmienione pliki do folderu tymczasowego
|
||||
```bash
|
||||
cp sciezka/do/pliku.php updates/0.20/temp_XXX/sciezka/do/pliku.php
|
||||
cp sciezka/do/pliku.php temp/temp_XXX/sciezka/do/pliku.php
|
||||
```
|
||||
|
||||
### 4. Utwórz plik ZIP z zawartości folderu (nie z samego folderu!)
|
||||
```powershell
|
||||
cd updates/0.20/temp_XXX
|
||||
cd temp/temp_XXX
|
||||
powershell -Command "Compress-Archive -Path '*' -DestinationPath '../ver_X.XXX.zip' -Force"
|
||||
```
|
||||
|
||||
### 5. Usuń folder tymczasowy
|
||||
```bash
|
||||
rm -rf updates/0.20/temp_XXX
|
||||
rm -rf temp/temp_XXX
|
||||
```
|
||||
|
||||
### 6. Zaktualizuj changelog.php
|
||||
@@ -82,15 +82,15 @@ Opis: Dodanie przycisku do zaznaczania zamówienia jako wysłane do trustmate.io
|
||||
|
||||
```bash
|
||||
# Utwórz strukturę w folderze tymczasowym
|
||||
mkdir -p updates/0.20/temp_234/autoload/admin/controls
|
||||
mkdir -p updates/0.20/temp_234/admin/templates/shop-order
|
||||
mkdir -p temp/temp_234/autoload/admin/controls
|
||||
mkdir -p temp/temp_234/admin/templates/shop-order
|
||||
|
||||
# Skopiuj pliki
|
||||
cp autoload/admin/controls/class.ShopOrder.php updates/0.20/temp_234/autoload/admin/controls/
|
||||
cp admin/templates/shop-order/order-details.php updates/0.20/temp_234/admin/templates/shop-order/
|
||||
cp autoload/admin/controls/class.ShopOrder.php temp/temp_234/autoload/admin/controls/
|
||||
cp admin/templates/shop-order/order-details.php temp/temp_234/admin/templates/shop-order/
|
||||
|
||||
# Utwórz ZIP z ZAWARTOŚCI folderu (ważne: wejdź do folderu i spakuj '*')
|
||||
cd updates/0.20/temp_234
|
||||
cd temp/temp_234
|
||||
powershell -Command "Compress-Archive -Path '*' -DestinationPath '../ver_0.234.zip' -Force"
|
||||
|
||||
# Wróć i usuń folder tymczasowy
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
<? $i = ( $this -> current_page - 1 ) * 10 + 1;?>
|
||||
<? foreach ( $this -> products as $product ):?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= $i++;?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="product-image">
|
||||
<? if ( $product['images'][0]['src'] ):?>
|
||||
<img src="<?= $product['images'][0]['src'];?>" alt="<?= $product['images'][0]['alt'];?>" class="img-responsive">
|
||||
<? else:?>
|
||||
<img src="/admin/layout/images/no-image.png" alt="Brak zdjęcia" class="img-responsive">
|
||||
<? endif;?>
|
||||
</div>
|
||||
<div class="product-name">
|
||||
<a href="/admin/shop_product/product_edit/id=<?= $product['id'];?>">
|
||||
<?= $product['languages']['pl']['name'];?>
|
||||
</a>
|
||||
<a href="#" class="text-muted duplicate-product" product-id="<?= $product['id'];?>">duplikuj</a>
|
||||
</div>
|
||||
<small class="text-muted"><?= \admin\factory\ShopProduct::product_categories( $product['id'] );?></small>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="text" class="product-price form-control text-right" product-id="<?= $product['id'];?>" value="<?= $product['price_brutto'];?>" style="max-width: 75px;">
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="text" class="product-price-promo form-control text-right" product-id="<?= $product['id'];?>" value="<?= $product['price_brutto_promo'];?>" style="max-width: 75px;">
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?= $product['promoted'] ? '<span class="text-success text-bold">tak</span>' : 'nie';?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?= $product['status'] ? 'tak' : '<span class="text-danger text-bold">nie</span>';?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="text-muted"><?= (int)\admin\factory\shopProduct::get_product_quantity_list( $product['id'] );?></span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?
|
||||
if ( $product['baselinker_product_name'] != "" ) {
|
||||
echo "<span title='" . htmlspecialchars( $product['baselinker_product_name'] ) . "'>" . mb_substr( $product['baselinker_product_name'], 0, 25, 'UTF-8' ) . "...</span>";
|
||||
echo "<br>";
|
||||
echo "<span class='text-danger baselinker-delete-linking' product-id='" . $product['id'] . "'>";
|
||||
echo "<i class='fa fa-times'></i>usuń powiązanie";
|
||||
echo "</span>";
|
||||
} else {
|
||||
echo "<span class='text-danger baselinker-product-search' product-id='" . $product['id'] . "'>";
|
||||
echo "nie przypisano <i class='fa fa-search'></i>";
|
||||
echo "</span>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?
|
||||
if ( $product['apilo_product_name'] != "" ) {
|
||||
echo "<span title='" . htmlspecialchars( $product['apilo_product_name'] ) . "'>" . mb_substr( $product['apilo_product_name'], 0, 25, "UTF-8" ) . "...</span>";
|
||||
echo "<br>";
|
||||
echo "<span class='text-danger apilo-delete-linking' product-id='" . $product['id'] . "'>";
|
||||
echo "<i class='fa fa-times'></i>usuń powiązanie";
|
||||
echo "</span>";
|
||||
} else {
|
||||
echo "<span class='text-danger apilo-product-search' product-id='" . $product['id'] . "'>";
|
||||
echo "nie przypisano <i class='fa fa-search'></i>";
|
||||
echo "</span>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<a href='/admin/shop_product/product_combination/product_id=<?= $product['id'];?>'>kombinacje (<?= \admin\factory\shopProduct::count_product_combinations( $product['id'] );?>)</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href='/admin/shop_product/product_edit/id=<?= $product['id'];?>'>edytuj</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href='/admin/shop_product/product_unarchive/product_id=<?= $product['id'];?>' class="product-unarchive">przywróć</a>
|
||||
</td>
|
||||
</tr>
|
||||
<? endforeach;?>
|
||||
@@ -1,488 +0,0 @@
|
||||
<div class="panel">
|
||||
<div class="panel-body">
|
||||
<a href="/admin/shop_product/product_edit/" class="btn btn-success select-all">Dodaj produkt</a>
|
||||
</div>
|
||||
<div class="panel-body pn">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover table-striped mbn" id="table-products">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 10px;">#</th>
|
||||
<th>Nazwa</th>
|
||||
<th class="text-center" style="width: 100px;">Cena</th>
|
||||
<th class="text-center" style="width: 100px;">Cena promocyjna</th>
|
||||
<th class="text-center" style="width: 25px;">Promowany</th>
|
||||
<th class="text-center" style="width: 25px;">Aktywny</th>
|
||||
<th class="text-center" style="width: 75px;">Stan MG</th>
|
||||
<th class="text-center" style="width: 100px;">Baselinker</th>
|
||||
<th class="text-center" style="width: 100px;">Apilo</th>
|
||||
<th class="text-center" style="width: 100px;">Kombinacje</th>
|
||||
<th class="text-center" style="width: 75px;">Edytuj</th>
|
||||
<th class="text-center" style="width: 75px;">Przywróć</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>
|
||||
<input type="text" class="form-control table-search" field_name="name|ean|sku" placeholder="szukaj..." value="<?= htmlspecialchars( $this -> query_array['name'], ENT_QUOTES, 'UTF-8');?>">
|
||||
</th>
|
||||
<th colspan="10"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="12">
|
||||
<ul class="pagination" pagination_max="<?= $this -> pagination_max;?>">
|
||||
<li>
|
||||
<a href="#" page="1" title="pierwsza strona">
|
||||
<i class="fa fa-angle-double-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="previous" page="<?= ( $this -> current_page - 1 > 1 ) ? ( $this -> current_page - 1 ) : 1;?>" title="poprzednia strona">
|
||||
<i class="fa fa-angle-left" title="poprzednia strona"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Strona <span id="current-page"><?= $this -> current_page;?></span> z <span id="max_page"><?= $this -> pagination_max;?></span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="next" page="<?= ( $this -> current_page + 1 < $this -> pagination_max ) ? ( $this -> current_page + 1 ) : $this -> pagination_max;?>" title="następna strona">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="last" page="<?= $this -> pagination_max;?>" title="ostatnia strona">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$( function() {
|
||||
ajax_load_products( <?= $this -> current_page;?>, null );
|
||||
|
||||
$( 'body' ).on( 'change', '.table-search', function() {
|
||||
ajax_load_products( 1 );
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.pagination a', function() {
|
||||
var current_page = $( this ).attr( 'page' );
|
||||
ajax_load_products( current_page );
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.product-unarchive', function(e) {
|
||||
e.preventDefault();
|
||||
var href = $( this ).attr( 'href' );
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz przywrócić wybrany produkt z archiwum?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'supervan',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-success',
|
||||
action: function() {}
|
||||
},
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
$( 'body' ).on( 'change', '.product-price-promo', function(e)
|
||||
{
|
||||
var price = $( this ).val();
|
||||
price = price.replace( ' ', '' );
|
||||
price = parseFloat( price.replace( ',', '.' ) * 1 );
|
||||
price = number_format( price, 2, '.', '' );
|
||||
|
||||
$( this ).val( price );
|
||||
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax(
|
||||
{
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_change_price_brutto_promo/',
|
||||
data:
|
||||
{
|
||||
product_id: product_id,
|
||||
price: price
|
||||
},
|
||||
beforeSend: function()
|
||||
{
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( data )
|
||||
{
|
||||
$( '#overlay' ).hide();
|
||||
|
||||
response = jQuery.parseJSON( data );
|
||||
|
||||
if ( response.status !== 'ok' )
|
||||
create_error( response.msg );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'change', '.product-price', function(e)
|
||||
{
|
||||
var price = $( this ).val();
|
||||
price = price.replace( ' ', '' );
|
||||
price = parseFloat( price.replace( ',', '.' ) * 1 );
|
||||
price = number_format( price, 2, '.', '' );
|
||||
|
||||
$( this ).val( price );
|
||||
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax(
|
||||
{
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_change_price_brutto/',
|
||||
data:
|
||||
{
|
||||
product_id: product_id,
|
||||
price: price
|
||||
},
|
||||
beforeSend: function()
|
||||
{
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( data )
|
||||
{
|
||||
$( '#overlay' ).hide();
|
||||
|
||||
response = jQuery.parseJSON( data );
|
||||
|
||||
if ( response.status !== 'ok' )
|
||||
create_error( response.msg );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.duplicate-product', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz wykonać duplikat produktu?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak (produkt bez kombinacji)',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = '/admin/shop_product/duplicate_product/product-id=' + product_id;
|
||||
}
|
||||
},
|
||||
confirm2: {
|
||||
text: 'Tak (produkt z KOMBINACJAMI)',
|
||||
btnClass: 'btn-primary',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = '/admin/shop_product/duplicate_product/product-id=' + product_id + '&combination=1';
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// apilo product search
|
||||
$( 'body' ).on( 'click', '.apilo-product-search', function() {
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/integrations/apilo_product_search/',
|
||||
data: {
|
||||
product_id: product_id
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'SUCCESS' ) {
|
||||
var html = '<div class="apilo-found-products">';
|
||||
html += '<p>Znaleziono ' + data.products.length + ' produktów</p>';
|
||||
html += '<select class="form-control apilo-product-select" product-id="' + product_id + '">';
|
||||
$.each( data.products, function( index, value ) {
|
||||
html += '<option value="' + value.id + '">' + value.name + ' SKU: ' + value.sku + '</option>';
|
||||
});
|
||||
html += '</select>';
|
||||
html += '<button class="btn btn-success apilo-product-select-save" product-id="' + product_id + '">Zapisz</button>';
|
||||
html += '</div>';
|
||||
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).closest( 'td' ).append( html );
|
||||
} else if ( data.status == 'error' ) {
|
||||
$.alert({
|
||||
title: 'Błąd',
|
||||
content: data.msg,
|
||||
type: 'red',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-exclamation-triangle',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'OK',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// delete apilo product linking
|
||||
$( 'body' ).on( 'click', '.apilo-delete-linking', function() {
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/apilo_product_select_delete/',
|
||||
data: {
|
||||
product_id: product_id
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'ok' ) {
|
||||
$( 'span.apilo-delete-linking[product-id="' + product_id + '"]' ).closest('td').html( '<span class="text-danger apilo-product-search" product-id="' + product_id + '">nie przypisano <i class="fa fa-search"></i></span>' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// delete baselinker product linking
|
||||
$( 'body' ).on( 'click', '.baselinker-delete-linking', function() {
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/baselinker_product_select_delete/',
|
||||
data: {
|
||||
product_id: product_id
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'ok' ) {
|
||||
$( 'span.baselinker-delete-linking[product-id="' + product_id + '"]' ).closest('td').html( '<span class="text-danger baselinker-product-search" product-id="' + product_id + '">nie przypisano <i class="fa fa-search"></i></span>' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.baselinker-product-search', function() {
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/baselinker_product_search/',
|
||||
data: {
|
||||
product_id: product_id
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'SUCCESS' ) {
|
||||
var html = '<div class="baselinker-found-products">';
|
||||
html += '<p>Znaleziono ' + data.products.length + ' produktów</p>';
|
||||
html += '<select class="form-control baselinker-product-select" product-id="' + product_id + '">';
|
||||
$.each( data.products, function( index, value ) {
|
||||
html += '<option value="' + value.product_id + '">' + value.name + ' SKU: ' + value.sku + '</option>';
|
||||
});
|
||||
html += '</select>';
|
||||
html += '<button class="btn btn-success baselinker-product-select-save" product-id="' + product_id + '">Zapisz</button>';
|
||||
html += '</div>';
|
||||
$( 'span.baselinker-product-search[product-id="' + product_id + '"]' ).closest( 'td' ).append( html );
|
||||
} else if ( data.status == 'error' ) {
|
||||
$.alert({
|
||||
title: 'Błąd',
|
||||
content: data.msg,
|
||||
type: 'red',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-exclamation-triangle',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'OK',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// apilo product select save
|
||||
$( 'body' ).on( 'click', '.apilo-product-select-save', function(){
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
var apilo_product_id = $( '.apilo-product-select[product-id="' + product_id + '"]' ).val();
|
||||
var apilo_product_name = $( '.apilo-product-select[product-id="' + product_id + '"] option:selected' ).text();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/apilo_product_select_save/',
|
||||
data: {
|
||||
product_id: product_id,
|
||||
apilo_product_id: apilo_product_id,
|
||||
apilo_product_name: apilo_product_name
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'ok' ) {
|
||||
$( '.apilo-product-select[product-id="' + product_id + '"]' ).closest( '.apilo-found-products' ).remove();
|
||||
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).html( '<span title="' + apilo_product_name + '">' + apilo_product_name.substr( 0, 25 ) + '...</span>' ).removeClass( 'apilo-product-search' ).removeClass( 'text-danger' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.baselinker-product-select-save', function(){
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
var baselinker_product_id = $( '.baselinker-product-select[product-id="' + product_id + '"]' ).val();
|
||||
var baselinker_product_name = $( '.baselinker-product-select[product-id="' + product_id + '"] option:selected' ).text();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/baselinker_product_select_save/',
|
||||
data: {
|
||||
product_id: product_id,
|
||||
baselinker_product_id: baselinker_product_id,
|
||||
baselinker_product_name: baselinker_product_name
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'ok' ) {
|
||||
$( '.baselinker-product-select[product-id="' + product_id + '"]' ).closest( '.baselinker-found-products' ).remove();
|
||||
$( 'span.baselinker-product-search[product-id="' + product_id + '"]' ).html( '<span title="' + baselinker_product_name + '">' + baselinker_product_name.substr( 0, 25 ) + '...</span>' ).removeClass( 'baselinker-product-search' ).removeClass( 'text-danger' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function ajax_load_products( current_page ) {
|
||||
var pagination_max = parseInt( $( '.pagination' ).attr( 'pagination_max' ) );
|
||||
|
||||
var query = '';
|
||||
$( '.table-search' ).each(function(){
|
||||
query += $( this ).attr( 'field_name' ) + '=' + $( this ).val() + '&';
|
||||
});
|
||||
|
||||
current_page = parseInt( current_page );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/ajax_load_products_archive/',
|
||||
data: {
|
||||
current_page: current_page,
|
||||
query: query
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
$( '#overlay' ).hide();
|
||||
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'ok' ) {
|
||||
$( '#table-products tbody' ).html( data.html );
|
||||
$( '.pagination .previous' ).attr( 'page', ( current_page - 1 > 1 ) ? ( current_page - 1 ) : 1 );
|
||||
$( '.pagination .next' ).attr( 'page', ( current_page + 1 < pagination_max ) ? ( current_page + 1 ) : pagination_max );
|
||||
$( '.pagination span' ).html( current_page );
|
||||
if ( data.pagination_max ) {
|
||||
$( '.pagination' ).attr( 'pagination_max', data.pagination_max );
|
||||
$( '.pagination #max_page' ).html( data.pagination_max );
|
||||
$( '.pagination .last' ).attr( 'page', data.pagination_max );
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
41
admin/templates/product_archive/products-list-table.php
Normal file
41
admin/templates/product_archive/products-list-table.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<? $i = ( $this -> current_page - 1 ) * 10 + 1;?>
|
||||
<? foreach ( $this -> products as $product ):?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= $i++;?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="product-image">
|
||||
<? if ( $product['images'][0]['src'] ):?>
|
||||
<img src="<?= $product['images'][0]['src'];?>" alt="<?= $product['images'][0]['alt'];?>" class="img-responsive">
|
||||
<? else:?>
|
||||
<img src="/admin/layout/images/no-image.png" alt="Brak zdjęcia" class="img-responsive">
|
||||
<? endif;?>
|
||||
</div>
|
||||
<div class="product-name">
|
||||
<a href="/admin/shop_product/product_edit/id=<?= $product['id'];?>">
|
||||
<?= $product['languages']['pl']['name'];?>
|
||||
</a>
|
||||
</div>
|
||||
<small class="text-muted"><?= \admin\factory\ShopProduct::product_categories( $product['id'] );?></small>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?= $product['price_brutto'];?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?= $product['price_brutto_promo'];?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="text-muted"><?= (int)\admin\factory\shopProduct::get_product_quantity_list( $product['id'] );?></span>
|
||||
</td>
|
||||
<td>
|
||||
<a href='/admin/shop_product/product_combination/product_id=<?= $product['id'];?>'>kombinacje (<?= \admin\factory\shopProduct::count_product_combinations( $product['id'] );?>)</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href='/admin/shop_product/product_edit/id=<?= $product['id'];?>'>edytuj</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href='/admin/product_archive/unarchive/product_id=<?= $product['id'];?>' class="product-unarchive">przywróć</a>
|
||||
</td>
|
||||
</tr>
|
||||
<? endforeach;?>
|
||||
152
admin/templates/product_archive/products-list.php
Normal file
152
admin/templates/product_archive/products-list.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<div class="panel">
|
||||
<div class="panel-body pn">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover table-striped mbn" id="table-products">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 10px;">#</th>
|
||||
<th>Nazwa</th>
|
||||
<th class="text-center" style="width: 100px;">Cena</th>
|
||||
<th class="text-center" style="width: 100px;">Cena promocyjna</th>
|
||||
<th class="text-center" style="width: 75px;">Stan MG</th>
|
||||
<th class="text-center" style="width: 100px;">Kombinacje</th>
|
||||
<th class="text-center" style="width: 75px;">Edytuj</th>
|
||||
<th class="text-center" style="width: 75px;">Przywróć</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>
|
||||
<input type="text" class="form-control table-search" field_name="name|ean|sku" placeholder="szukaj..." value="<?= htmlspecialchars( $this -> query_array['name'] ?? '', ENT_QUOTES, 'UTF-8');?>">
|
||||
</th>
|
||||
<th colspan="6"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="8">
|
||||
<ul class="pagination" pagination_max="<?= $this -> pagination_max;?>">
|
||||
<li>
|
||||
<a href="#" page="1" title="pierwsza strona">
|
||||
<i class="fa fa-angle-double-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="previous" page="<?= ( $this -> current_page - 1 > 1 ) ? ( $this -> current_page - 1 ) : 1;?>" title="poprzednia strona">
|
||||
<i class="fa fa-angle-left" title="poprzednia strona"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Strona <span id="current-page"><?= $this -> current_page;?></span> z <span id="max_page"><?= $this -> pagination_max;?></span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="next" page="<?= ( $this -> current_page + 1 < $this -> pagination_max ) ? ( $this -> current_page + 1 ) : $this -> pagination_max;?>" title="następna strona">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="last" page="<?= $this -> pagination_max;?>" title="ostatnia strona">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$( function() {
|
||||
ajax_load_products( <?= $this -> current_page;?> );
|
||||
|
||||
$( 'body' ).on( 'change', '.table-search', function() {
|
||||
ajax_load_products( 1 );
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.pagination a', function() {
|
||||
var current_page = $( this ).attr( 'page' );
|
||||
ajax_load_products( current_page );
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.product-unarchive', function(e) {
|
||||
e.preventDefault();
|
||||
var href = $( this ).attr( 'href' );
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz przywrócić wybrany produkt z archiwum?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'supervan',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-success',
|
||||
action: function() {}
|
||||
},
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
function ajax_load_products( current_page ) {
|
||||
var pagination_max = parseInt( $( '.pagination' ).attr( 'pagination_max' ) );
|
||||
|
||||
var query = '';
|
||||
$( '.table-search' ).each(function(){
|
||||
query += $( this ).attr( 'field_name' ) + '=' + $( this ).val() + '&';
|
||||
});
|
||||
|
||||
current_page = parseInt( current_page );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/ajax_load_products_archive/',
|
||||
data: {
|
||||
current_page: current_page,
|
||||
query: query
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
$( '#overlay' ).hide();
|
||||
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'ok' ) {
|
||||
$( '#table-products tbody' ).html( data.html );
|
||||
$( '.pagination .previous' ).attr( 'page', ( current_page - 1 > 1 ) ? ( current_page - 1 ) : 1 );
|
||||
$( '.pagination .next' ).attr( 'page', ( current_page + 1 < pagination_max ) ? ( current_page + 1 ) : pagination_max );
|
||||
$( '.pagination span' ).html( current_page );
|
||||
if ( data.pagination_max ) {
|
||||
$( '.pagination' ).attr( 'pagination_max', data.pagination_max );
|
||||
$( '.pagination #max_page' ).html( data.pagination_max );
|
||||
$( '.pagination .last' ).attr( 'page', data.pagination_max );
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -127,7 +127,7 @@
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/admin/archive/products_list/">
|
||||
<a href="/admin/product_archive/products_list/">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>Produkty
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -102,4 +102,32 @@ class ProductRepository
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Przywraca produkt z archiwum (wraz z kombinacjami)
|
||||
*
|
||||
* @param int $productId ID produktu
|
||||
* @return bool Czy operacja się powiodła
|
||||
*/
|
||||
public function unarchive(int $productId): bool
|
||||
{
|
||||
$this->db->update( 'pp_shop_products', [ 'status' => 1, 'archive' => 0 ], [ 'id' => $productId ] );
|
||||
$this->db->update( 'pp_shop_products', [ 'status' => 1, 'archive' => 0 ], [ 'parent_id' => $productId ] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Przenosi produkt do archiwum (wraz z kombinacjami)
|
||||
*
|
||||
* @param int $productId ID produktu
|
||||
* @return bool Czy operacja się powiodła
|
||||
*/
|
||||
public function archive(int $productId): bool
|
||||
{
|
||||
$this->db->update( 'pp_shop_products', [ 'status' => 0, 'archive' => 1 ], [ 'id' => $productId ] );
|
||||
$this->db->update( 'pp_shop_products', [ 'status' => 0, 'archive' => 1 ], [ 'parent_id' => $productId ] );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
47
autoload/admin/Controllers/ProductArchiveController.php
Normal file
47
autoload/admin/Controllers/ProductArchiveController.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace admin\Controllers;
|
||||
|
||||
use Domain\Product\ProductRepository;
|
||||
|
||||
class ProductArchiveController
|
||||
{
|
||||
private ProductRepository $productRepository;
|
||||
|
||||
public function __construct(ProductRepository $productRepository)
|
||||
{
|
||||
$this->productRepository = $productRepository;
|
||||
}
|
||||
|
||||
public function list(): string
|
||||
{
|
||||
$current_page = \S::get_session( 'archive_products_list_current_page' );
|
||||
|
||||
if ( !$current_page ) {
|
||||
$current_page = 1;
|
||||
\S::set_session( 'archive_products_list_current_page', $current_page );
|
||||
}
|
||||
|
||||
$query = \S::get_session( 'archive_products_list_query' );
|
||||
$query_array = [];
|
||||
if ( $query ) {
|
||||
parse_str( $query, $query_array );
|
||||
}
|
||||
|
||||
return \Tpl::view( 'product_archive/products-list', [
|
||||
'current_page' => $current_page,
|
||||
'query_array' => $query_array,
|
||||
'pagination_max' => ceil( \admin\factory\ShopProduct::count_product( [ 'archive' => 1 ] ) / 10 )
|
||||
] );
|
||||
}
|
||||
|
||||
public function unarchive(): void
|
||||
{
|
||||
if ( $this->productRepository->unarchive( (int) \S::get( 'product_id' ) ) )
|
||||
\S::alert( 'Produkt został przywrócony z archiwum.' );
|
||||
else
|
||||
\S::alert( 'Podczas przywracania produktu z archiwum wystąpił błąd. Proszę spróbować ponownie' );
|
||||
|
||||
header( 'Location: /admin/product_archive/products_list/' );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -211,6 +211,13 @@ class Site
|
||||
new \Domain\Settings\SettingsRepository()
|
||||
);
|
||||
},
|
||||
'ProductArchive' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ProductArchiveController(
|
||||
new \Domain\Product\ProductRepository( $mdb )
|
||||
);
|
||||
},
|
||||
];
|
||||
|
||||
return self::$newControllers;
|
||||
@@ -246,6 +253,7 @@ class Site
|
||||
'clear_cache' => 'clearCache',
|
||||
'clear_cache_ajax' => 'clearCacheAjax',
|
||||
'settings_save' => 'save',
|
||||
'products_list' => 'list',
|
||||
];
|
||||
|
||||
public static function route()
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?
|
||||
namespace admin\controls;
|
||||
|
||||
/**
|
||||
* @deprecated Użyj \admin\Controllers\ProductArchiveController
|
||||
* Klasa zachowana jako fallback dla starego URL /admin/archive/products_list/
|
||||
*/
|
||||
class Archive {
|
||||
static public function products_list() {
|
||||
|
||||
@@ -16,7 +21,7 @@ class Archive {
|
||||
parse_str( $query, $query_array );
|
||||
}
|
||||
|
||||
return \Tpl::view( 'archive/products-list', [
|
||||
return \Tpl::view( 'product_archive/products-list', [
|
||||
'current_page' => $current_page,
|
||||
'query_array' => $query_array,
|
||||
'pagination_max' => ceil( \admin\factory\ShopProduct::count_product() / 10 )
|
||||
|
||||
@@ -203,7 +203,7 @@ class ShopProduct
|
||||
else
|
||||
\S::alert( 'Podczas przywracania produktu z archiwum wystąpił błąd. Proszę spróbować ponownie' );
|
||||
|
||||
header( 'Location: /admin/archive/products_list/' );
|
||||
header( 'Location: /admin/product_archive/products_list/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ class ShopProduct
|
||||
$response = [
|
||||
'status' => 'ok',
|
||||
'pagination_max' => ceil( $products['products_count'] / 10 ),
|
||||
'html' => \Tpl::view( 'archive/products-list-table', [
|
||||
'html' => \Tpl::view( 'product_archive/products-list-table', [
|
||||
'products' => $products['products'],
|
||||
'current_page' => \S::get( 'current_page' ),
|
||||
] )
|
||||
|
||||
@@ -482,34 +482,32 @@ class ShopProduct
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$search = '';
|
||||
|
||||
if ( $query )
|
||||
{
|
||||
$search = '';
|
||||
$query_array = [];
|
||||
|
||||
parse_str( $query, $query_array );
|
||||
|
||||
foreach ( $query_array as $key => $val ) {
|
||||
$search .= ' AND ' . $key . ' LIKE \'%' . $val . '%\'';
|
||||
if ( $val !== '' )
|
||||
$search .= ' AND ' . $key . ' LIKE \'%' . $val . '%\'';
|
||||
}
|
||||
|
||||
$results = $mdb -> query( 'SELECT '
|
||||
. 'DISTINCT( psp.id )'
|
||||
. 'FROM '
|
||||
. 'pp_shop_products AS psp '
|
||||
. 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
|
||||
. 'WHERE archive = 1 AND parent_id IS NULL ' . $search . ' ORDER BY id DESC LIMIT ' . ( $current_page - 1 ) * 10 . ', 10' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
$results2 = $mdb -> query( 'SELECT '
|
||||
. 'COUNT( DISTINCT( psp.id ) ) AS products_count '
|
||||
. 'FROM '
|
||||
. 'pp_shop_products AS psp '
|
||||
. 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
|
||||
. 'WHERE archive = 1 AND parent_id IS NULL ' . $search ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
} else {
|
||||
$results = $mdb -> query( 'SELECT id FROM pp_shop_products WHERE parent_id IS NULL ORDER BY id DESC LIMIT ' . ( $current_page - 1 ) * 10 . ', 10' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
$results2 = $mdb -> query( 'SELECT COUNT( id ) AS products_count FROM pp_shop_products WHERE parent_id IS NULL' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
}
|
||||
|
||||
$results = $mdb -> query( 'SELECT '
|
||||
. 'DISTINCT( psp.id )'
|
||||
. 'FROM '
|
||||
. 'pp_shop_products AS psp '
|
||||
. 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
|
||||
. 'WHERE archive = 1 AND parent_id IS NULL ' . $search . ' ORDER BY id DESC LIMIT ' . ( $current_page - 1 ) * 10 . ', 10' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
$results2 = $mdb -> query( 'SELECT '
|
||||
. 'COUNT( DISTINCT( psp.id ) ) AS products_count '
|
||||
. 'FROM '
|
||||
. 'pp_shop_products AS psp '
|
||||
. 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
|
||||
. 'WHERE archive = 1 AND parent_id IS NULL ' . $search ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
|
||||
if ( is_array( $results ) ) foreach ( $results as $row ) {
|
||||
$products[] = \admin\factory\ShopProduct::product_details( $row['id'] );
|
||||
}
|
||||
|
||||
@@ -259,4 +259,92 @@ class ProductRepositoryTest extends TestCase
|
||||
$this->assertIsInt($quantity); // Sprawdzamy czy konwersja na int zadziałała
|
||||
$this->assertEquals(25, $quantity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test przywracania produktu z archiwum
|
||||
*/
|
||||
public function testUnarchiveUpdatesProductAndChildren()
|
||||
{
|
||||
// Arrange
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$mockDb->expects($this->exactly(2))
|
||||
->method('update')
|
||||
->withConsecutive(
|
||||
[
|
||||
$this->equalTo('pp_shop_products'),
|
||||
$this->equalTo(['status' => 1, 'archive' => 0]),
|
||||
$this->equalTo(['id' => 123])
|
||||
],
|
||||
[
|
||||
$this->equalTo('pp_shop_products'),
|
||||
$this->equalTo(['status' => 1, 'archive' => 0]),
|
||||
$this->equalTo(['parent_id' => 123])
|
||||
]
|
||||
);
|
||||
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
// Act
|
||||
$result = $repository->unarchive(123);
|
||||
|
||||
// Assert
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test przenoszenia produktu do archiwum
|
||||
*/
|
||||
public function testArchiveUpdatesProductAndChildren()
|
||||
{
|
||||
// Arrange
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$mockDb->expects($this->exactly(2))
|
||||
->method('update')
|
||||
->withConsecutive(
|
||||
[
|
||||
$this->equalTo('pp_shop_products'),
|
||||
$this->equalTo(['status' => 0, 'archive' => 1]),
|
||||
$this->equalTo(['id' => 456])
|
||||
],
|
||||
[
|
||||
$this->equalTo('pp_shop_products'),
|
||||
$this->equalTo(['status' => 0, 'archive' => 1]),
|
||||
$this->equalTo(['parent_id' => 456])
|
||||
]
|
||||
);
|
||||
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
// Act
|
||||
$result = $repository->archive(456);
|
||||
|
||||
// Assert
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test że unarchive zwraca bool
|
||||
*/
|
||||
public function testUnarchiveReturnsBool()
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
$result = $repository->unarchive(1);
|
||||
$this->assertIsBool($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test że archive zwraca bool
|
||||
*/
|
||||
public function testArchiveReturnsBool()
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
$result = $repository->archive(1);
|
||||
$this->assertIsBool($result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace Tests\Unit\admin\Controllers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use admin\Controllers\ProductArchiveController;
|
||||
use Domain\Product\ProductRepository;
|
||||
|
||||
class ProductArchiveControllerTest extends TestCase
|
||||
{
|
||||
private $mockRepository;
|
||||
private $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->mockRepository = $this->createMock(ProductRepository::class);
|
||||
$this->controller = new ProductArchiveController($this->mockRepository);
|
||||
}
|
||||
|
||||
public function testConstructorAcceptsRepository(): void
|
||||
{
|
||||
$controller = new ProductArchiveController($this->mockRepository);
|
||||
$this->assertInstanceOf(ProductArchiveController::class, $controller);
|
||||
}
|
||||
|
||||
public function testHasListMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'list'));
|
||||
}
|
||||
|
||||
public function testHasUnarchiveMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'unarchive'));
|
||||
}
|
||||
|
||||
public function testListMethodReturnType(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType());
|
||||
}
|
||||
|
||||
public function testUnarchiveMethodReturnType(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('unarchive')->getReturnType());
|
||||
}
|
||||
|
||||
public function testConstructorRequiresProductRepository(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(ProductArchiveController::class);
|
||||
$constructor = $reflection->getConstructor();
|
||||
$params = $constructor->getParameters();
|
||||
|
||||
$this->assertCount(1, $params);
|
||||
$this->assertEquals('Domain\Product\ProductRepository', $params[0]->getType()->getName());
|
||||
}
|
||||
}
|
||||
BIN
updates/0.20/ver_0.241.zip
Normal file
BIN
updates/0.20/ver_0.241.zip
Normal file
Binary file not shown.
@@ -1,3 +1,10 @@
|
||||
<b>ver. 0.241</b><br />
|
||||
- NEW - refaktoryzacja: admin\Controllers\ProductArchiveController - archiwum produktów z DI
|
||||
- NEW - ProductRepository::archive(), unarchive() - operacje archiwizacji w repozytorium
|
||||
- FIX - naprawiono SQL w liście archiwum (puste wyszukiwanie filtrowało wszystkie wyniki)
|
||||
- FIX - naprawiono brakujący filtr archive = 1 w zapytaniu bez wyszukiwania
|
||||
- UPDATE - wyczyszczono szablony archiwum (usunięto zbędne funkcje: apilo, baselinker, duplikowanie)
|
||||
<hr>
|
||||
<b>ver. 0.240</b><br />
|
||||
- NEW - refaktoryzacja: Domain\Settings\SettingsRepository + admin\Controllers\SettingsController (architektura Domain-Driven)
|
||||
- NEW - refaktoryzacja: Domain\Cache\CacheRepository - czyszczenie cache z obsługą Redis
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?
|
||||
$current_ver = 240;
|
||||
$current_ver = 241;
|
||||
|
||||
for ($i = 1; $i <= $current_ver; $i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user