Dodano pomiar czasu wykonania skryptu importu produktów oraz optymalizacja logiki synchronizacji i aktualizacji produktów w PrestaShop.

This commit is contained in:
2025-12-21 15:07:29 +01:00
parent d963ce5fbe
commit 45b32a5ee2

View File

@@ -2,6 +2,9 @@
// URL do pliku XML: // URL do pliku XML:
// https://sollux-lighting.com/product_feed/sollux/XML_polski_PLN_wszystkie_vip.xml // https://sollux-lighting.com/product_feed/sollux/XML_polski_PLN_wszystkie_vip.xml
// START POMIARU CAŁEGO SKRYPTU
$scriptStartTime = microtime(true);
// Załaduj konfigurację PrestaShop // Załaduj konfigurację PrestaShop
include(dirname(__FILE__).'/config/config.inc.php'); include(dirname(__FILE__).'/config/config.inc.php');
include(dirname(__FILE__).'/init.php'); include(dirname(__FILE__).'/init.php');
@@ -13,17 +16,24 @@ $context = Context::getContext();
// ============================================ // ============================================
// ID Producenta (Sollux Lighting) - podaj ID z PrestaShop // ID Producenta (Sollux Lighting) - podaj ID z PrestaShop
$configManufacturerId = 1; $configManufacturerId = 198;
// ID Grupy Podatkowej (np. dla 23% VAT) - sprawdź w Lokalizacja > Podatki > Reguły podatkowe // ID Grupy Podatkowej (np. dla 23% VAT)
$configTaxRulesGroupId = 1; $configTaxRulesGroupId = 1;
// ID Kategorii domyślnej, do której mają trafić produkty (np. "Kinkiety" lub ogólna) // ID Kategorii domyślnej, do której mają trafić produkty
$configDefaultCategoryId = 12; $configDefaultCategoryId = 1072;
// Czy włączyć produkt po imporcie? // Czy włączyć produkt po imporcie?
$configActive = 1; $configActive = 1;
// --- KONFIGURACJA CECHY (FEATURE) ---
// ID Cechy do przypisania (np. 20)
$configFeatureId = 20;
// ID Wartości Cechy do przypisania (np. 19002)
$configFeatureValueId = 19002;
// ============================================ // ============================================
// Sprawdzenie trybu działania // Sprawdzenie trybu działania
@@ -38,13 +48,21 @@ if (!$modeAdd && !$modeUpdate && !$modeSynch) {
// Plik logu // Plik logu
$logFile = __DIR__ . '/sollux_import_log.csv'; $logFile = __DIR__ . '/sollux_import_log.csv';
// Wczytanie XML // MIERZENIE: Ładowanie XML
$tXmlStart = microtime(true);
$xmlUrl = 'https://sollux-lighting.com/product_feed/sollux/XML_polski_PLN_wszystkie_vip.xml'; $xmlUrl = 'https://sollux-lighting.com/product_feed/sollux/XML_polski_PLN_wszystkie_vip.xml';
$xml = simplexml_load_file($xmlUrl) or die("Error: Cannot create object from XML"); $xml = simplexml_load_file($xmlUrl) or die("Error: Cannot create object from XML");
$tXmlEnd = microtime(true);
$tXmlDuration = $tXmlEnd - $tXmlStart; // Czas parsowania XML
// === FUNKCJE POMOCNICZE === // === FUNKCJE POMOCNICZE ===
// Formatowanie czasu do wyświetlania
function formatTime($time) {
return number_format($time, 4) . ' s';
}
// Tworzenie pola multilang (wymagane przez Prestę) // Tworzenie pola multilang (wymagane przez Prestę)
function createMultiLangField($field) { function createMultiLangField($field) {
$languages = Language::getLanguages(false); $languages = Language::getLanguages(false);
@@ -193,13 +211,12 @@ if ($modeSynch) {
$db->execute('INSERT IGNORE INTO `'._DB_PREFIX_.'sollux_temp` (`sku`) VALUES '.implode(',', $sqlValues)); $db->execute('INSERT IGNORE INTO `'._DB_PREFIX_.'sollux_temp` (`sku`) VALUES '.implode(',', $sqlValues));
} }
// Wyłącz produkty, których nie ma w XML a są w bazie (i są powiązane z tym producentem, opcjonalnie) // Wyłącz produkty, których nie ma w XML a są w bazie
// Zakładamy, że sprawdzamy tylko produkty, które mają SKU.
$sqlDisable = 'UPDATE `'._DB_PREFIX_.'product` p $sqlDisable = 'UPDATE `'._DB_PREFIX_.'product` p
LEFT JOIN `'._DB_PREFIX_.'sollux_temp` t ON p.reference = t.sku LEFT JOIN `'._DB_PREFIX_.'sollux_temp` t ON p.reference = t.sku
SET p.active = 0 SET p.active = 0
WHERE t.sku IS NULL WHERE t.sku IS NULL
AND p.id_manufacturer = '.(int)$configManufacturerId; // Bezpiecznik: wyłączamy tylko produkty tego producenta AND p.id_manufacturer = '.(int)$configManufacturerId;
$db->execute($sqlDisable); $db->execute($sqlDisable);
echo "<p>Wyłączono produkty niedostępne w feedzie.</p>"; echo "<p>Wyłączono produkty niedostępne w feedzie.</p>";
@@ -215,7 +232,9 @@ if ($modeSynch) {
// Posprzątaj // Posprzątaj
$db->execute('DROP TABLE `'._DB_PREFIX_.'sollux_temp`'); $db->execute('DROP TABLE `'._DB_PREFIX_.'sollux_temp`');
echo '<p>Zakończono synchronizację.</p>';
$totalTime = microtime(true) - $scriptStartTime;
echo '<p>Zakończono synchronizację. Całkowity czas: '.formatTime($totalTime).'</p>';
exit; exit;
} }
@@ -225,12 +244,12 @@ if ($modeUpdate) {
$today = date('Y-m-d'); $today = date('Y-m-d');
$updatedCount = 0; $updatedCount = 0;
// Wczytaj log, aby nie aktualizować tego samego produktu wielokrotnie tego samego dnia // Wczytaj log
$processedSkus = []; $processedSkus = [];
if (file_exists($logFile)) { if (file_exists($logFile)) {
$lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); $lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) { foreach ($lines as $line) {
$data = explode(';', $line); // Date;SKU $data = explode(';', $line);
if (isset($data[0]) && $data[0] == $today && isset($data[1])) { if (isset($data[0]) && $data[0] == $today && isset($data[1])) {
$processedSkus[trim($data[1])] = true; $processedSkus[trim($data[1])] = true;
} }
@@ -243,39 +262,43 @@ if ($modeUpdate) {
$sku = trim((string)$productNode->sku); $sku = trim((string)$productNode->sku);
if (empty($sku)) continue; if (empty($sku)) continue;
// Jeśli już zrobiony dzisiaj - pomiń
if (isset($processedSkus[$sku])) continue; if (isset($processedSkus[$sku])) continue;
// MIERZENIE: Szukanie produktu
$tStartCheck = microtime(true);
$product = findProductByReference($sku); $product = findProductByReference($sku);
$tCheckDuration = microtime(true) - $tStartCheck;
if (!$product) { if (!$product) {
// Produkt nie istnieje w bazie - w trybie update pomijamy
continue; continue;
} }
// --- Aktualizacja Ceny --- // MIERZENIE: Update produktu
$tStartUpdate = microtime(true);
$priceGross = parsePrice($productNode->cena_detaliczna_brutto_pln); $priceGross = parsePrice($productNode->cena_detaliczna_brutto_pln);
$priceNet = Tools::ps_round($priceGross / 1.23, 6); // Zakładamy 23% VAT od brutto $priceNet = Tools::ps_round($priceGross / 1.23, 6);
$product->price = $priceNet; $product->price = $priceNet;
$product->active = 1; // Przywracamy aktywność przy update $product->active = 1;
// --- Aktualizacja Stanu (Domyślnie 100 jeśli jest w pliku) ---
// XML nie ma pola "qty", ale ma "paczkomat" i "orientacyjny czas".
// Zakładamy: jest w XML = dostępny.
StockAvailable::setQuantity($product->id, 0, 100); StockAvailable::setQuantity($product->id, 0, 100);
$product->update(); $product->update();
$tUpdateDuration = microtime(true) - $tStartUpdate;
// Logowanie // Logowanie
file_put_contents($logFile, $today.';'.$sku.PHP_EOL, FILE_APPEND); file_put_contents($logFile, $today.';'.$sku.PHP_EOL, FILE_APPEND);
echo "<p>Zaktualizowano: $sku (Cena netto: $priceNet)</p>"; echo "<p>Zaktualizowano: $sku (Cena netto: $priceNet)</p>";
$updatedCount++;
// Aktualizujemy 1 produkt na wywołanie, żeby nie przekroczyć czasu wykonywania? // RAPORT UPDATE
// W poprzednim skrypcie był break. Jeśli masz crona co minutę, odkomentuj break. echo '<div style="background:#f0f0f0; border:1px solid #ccc; padding:10px; margin:10px 0;">';
// Jeśli odpalasz ręcznie w przeglądarce, break spowoduje konieczność ciągłego odświeżania. echo '<strong>Raport czasu (Update):</strong><br>';
// Zostawiam break dla bezpieczeństwa, odświeżaj stronę (meta reload na dole). echo 'Szukanie w bazie: ' . formatTime($tCheckDuration) . '<br>';
echo 'Zapis update (SQL): ' . formatTime($tUpdateDuration) . '<br>';
echo '</div>';
$updatedCount++;
break; break;
} }
@@ -296,41 +319,37 @@ if ($modeAdd) {
$sku = trim((string)$productNode->sku); $sku = trim((string)$productNode->sku);
if (empty($sku)) continue; if (empty($sku)) continue;
// Sprawdź czy produkt już istnieje // MIERZENIE: Sprawdzenie istnienia
if (findProductByReference($sku)) { $tStartCheck = microtime(true);
// Produkt istnieje, w trybie ADD nic nie robimy (ewentualnie można zrobić update) $exists = findProductByReference($sku);
$tCheckDuration = microtime(true) - $tStartCheck;
if ($exists) {
continue; continue;
} }
// === TWORZENIE NOWEGO PRODUKTU === // MIERZENIE: Przygotowanie obiektu (Pamięć)
$tStartPrep = microtime(true);
$product = new Product(); $product = new Product();
$product->reference = $sku; $product->reference = $sku;
$product->ean13 = trim((string)$productNode->ean); $product->ean13 = trim((string)$productNode->ean);
$product->name = createMultiLangField(trim((string)$productNode->nazwa_produktu)); $product->name = createMultiLangField(trim((string)$productNode->nazwa_produktu));
// Kategoria i Producent
$product->id_category_default = (int)$configDefaultCategoryId; $product->id_category_default = (int)$configDefaultCategoryId;
$product->id_manufacturer = (int)$configManufacturerId; $product->id_manufacturer = (int)$configManufacturerId;
$product->id_tax_rules_group = (int)$configTaxRulesGroupId; // VAT $product->id_tax_rules_group = (int)$configTaxRulesGroupId;
// Cena
$priceGross = parsePrice($productNode->cena_detaliczna_brutto_pln); $priceGross = parsePrice($productNode->cena_detaliczna_brutto_pln);
$product->price = Tools::ps_round($priceGross / 1.23, 6); $product->price = Tools::ps_round($priceGross / 1.23, 6);
// Wymiary i Waga
$product->width = (float)$productNode->szerokosc; $product->width = (float)$productNode->szerokosc;
$product->height = (float)$productNode->wysokosc; $product->height = (float)$productNode->wysokosc;
$product->depth = (float)$productNode->dlugosc; $product->depth = (float)$productNode->dlugosc;
$product->weight = (float)$productNode->waga_produktu; $product->weight = (float)$productNode->waga_produktu;
// Opisy
$shortDesc = trim((string)$productNode->opis_krotki_html); $shortDesc = trim((string)$productNode->opis_krotki_html);
$longDescRaw = trim((string)$productNode->opis_dlugi_korzysci_html); $longDescRaw = trim((string)$productNode->opis_dlugi_korzysci_html);
// Generowanie tabeli specyfikacji z atrybutów
$attributesHtml = buildAttributesHtml($productNode->attributes); $attributesHtml = buildAttributesHtml($productNode->attributes);
// Sklejenie opisu: Opis marketingowy + Tabela atrybutów
$finalLongDesc = $longDescRaw . '<br><hr>' . $attributesHtml; $finalLongDesc = $longDescRaw . '<br><hr>' . $attributesHtml;
$product->description_short = createMultiLangField($shortDesc); $product->description_short = createMultiLangField($shortDesc);
@@ -338,29 +357,63 @@ if ($modeAdd) {
$product->link_rewrite = createLinkRewrite((string)$productNode->nazwa_produktu); $product->link_rewrite = createLinkRewrite((string)$productNode->nazwa_produktu);
$product->active = (int)$configActive; $product->active = (int)$configActive;
$tPrepDuration = microtime(true) - $tStartPrep;
// Dodanie do bazy // MIERZENIE: Dodanie do bazy (INSERT)
if ($product->add()) { $tStartSave = microtime(true);
// Przypisanie do kategorii (domyślna + drzewo) $saveResult = $product->add();
$tSaveDuration = microtime(true) - $tStartSave;
if ($saveResult) {
// Przypisanie do kategorii i stanu
$product->addToCategories([(int)$configDefaultCategoryId]); $product->addToCategories([(int)$configDefaultCategoryId]);
// Ustawienie stanu magazynowego (100)
StockAvailable::setQuantity($product->id, 0, 100); StockAvailable::setQuantity($product->id, 0, 100);
// === ZDJĘCIA === // Dodanie Cechy
// Loop przez image_1 do image_25 if (!empty($configFeatureId) && !empty($configFeatureValueId)) {
Product::addFeatureProductImport(
(int)$product->id,
(int)$configFeatureId,
(int)$configFeatureValueId
);
}
// MIERZENIE: Zdjęcia (Pobieranie + Resize)
$tStartImages = microtime(true);
$imgCount = 0;
for ($i = 1; $i <= 25; $i++) { for ($i = 1; $i <= 25; $i++) {
$imgTag = 'image_' . $i; $imgTag = 'image_' . $i;
$imgUrl = (string)$productNode->$imgTag; $imgUrl = (string)$productNode->$imgTag;
if (!empty($imgUrl)) { if (!empty($imgUrl)) {
addProductImage($product->id, $imgUrl); if (addProductImage($product->id, $imgUrl)) {
$imgCount++;
}
} }
} }
$tImagesDuration = microtime(true) - $tStartImages;
echo "<p style='color:green'>Dodano produkt: " . $product->name[Context::getContext()->language->id] . " ($sku)</p>"; echo "<p style='color:green'>Dodano produkt: " . $product->name[Context::getContext()->language->id] . " ($sku)</p>";
// --- RAPORT WYDAJNOŚCI ---
$totalProductTime = microtime(true) - $tStartCheck; // Czas od sprawdzenia do końca zdjęć
echo '<div style="border: 2px solid #333; padding: 15px; background: #fff; margin: 15px 0; font-family: monospace;">';
echo '<h3 style="margin-top:0;">⏱️ Raport czasu dla: '.$sku.'</h3>';
echo '<ul>';
echo '<li><strong>Wczytanie XML (cały plik):</strong> '.formatTime($tXmlDuration).'</li>';
echo '<li><strong>Szukanie w bazie (czy istnieje):</strong> '.formatTime($tCheckDuration).'</li>';
echo '<li><strong>Przygotowanie danych (obiekt):</strong> '.formatTime($tPrepDuration).'</li>';
echo '<li><strong>Zapis produktu do SQL (INSERT):</strong> '.formatTime($tSaveDuration).'</li>';
echo '<li><strong>ZDJĘCIA ('.$imgCount.' szt.) - Pobranie + Resize:</strong> <strong style="color:red">'.formatTime($tImagesDuration).'</strong></li>';
echo '<li>----------------------------------</li>';
echo '<li><strong>ŁĄCZNIE DLA TEGO PRODUKTU:</strong> <strong>'.formatTime($totalProductTime).'</strong></li>';
echo '</ul>';
echo '</div>';
// --------------------------
$addedCount++; $addedCount++;
// Break po dodaniu jednego, aby odciążyć serwer (skrypt musi być wywoływany cyklicznie) // Break po dodaniu jednego
break; break;
} else { } else {
echo "<p style='color:red'>Błąd podczas dodawania produktu: $sku</p>"; echo "<p style='color:red'>Błąd podczas dodawania produktu: $sku</p>";