Komunikat „Produkt nie jest dostępny” przy przejściu do zamówienia w PrestaShop, choć produkt wchodzi do koszyka bez problemu, najczęściej oznacza, że produkt ma w bazie active = 0. Walidacja koszyka sprawdza to pole dopiero przy checkoucie. Poniżej pokazujemy, jak to znaleźliśmy w sklepie z modułem gwarancji, kiedy błąd zaczął blokować zamówienia i jakie dwie dodatkowe wady wyszły przy okazji.
Dlaczego PrestaShop dodaje produkt do koszyka, ale blokuje zamówienie?
Klient wybierał gwarancję na karcie produktu i dodawał ją do koszyka. Produkt i gwarancja wchodziły, a w kroku zamówienia pojawiał się komunikat, że produkt nie jest dostępny, i nie dało się iść dalej. Objaw był mylący, bo koszyk wyglądał poprawnie.
Łańcuch w kodzie PrestaShop 1.7:
Cart::getProducts()czytaproduct_shop.active.Cart::checkQuantities()ma jako pierwszy warunek!$product['active']i zwraca takie produkty jako błędne.CartController::areProductsAvailable()pokazuje komunikat. Komunikat dla nieaktywnego produktu pochodzi z innej gałęzi niż komunikat o złej ilości, więc warto sprawdzić, który z nich widzisz.
Dodanie do koszyka działało, bo updateQty() pola active nie sprawdza. Walidacja jest dopiero przy przejściu do zamówienia.
Kiedy błąd zaczął blokować zamówienia?
Produkt miał active = 0 od dawna, celowo, razem z visibility = none, żeby gwarancja nie pokazywała się w katalogu, i sprzedawał się normalnie. Zamówienia stanęły dzień po poprawce w integracji z systemem sprzedaży, która dopisała brakujące powiązania kombinacji z atrybutami: ostatnia sprzedaż gwarancji była dzień przed poprawką.
Poprawka integracji była prawidłowa i to ona zmieniła zachowanie produktu w koszyku, ale blokadę powodowało wyłączone active. Przez kolejne tygodnie sprzedaż gwarancji spadła niemal do zera.
Jak odróżnić brak stanu od wyłączonego produktu?
Pierwsza hipoteza dotyczyła zerowego stanu magazynowego kombinacji i okazała się błędna. Kombinacje miały out_of_stock = 1 w ps_stock_available, czyli sprzedaż bez stanu była dozwolona, a allow_oosp zwracało prawdę. Blokowało wyłącznie pole active. Zasada: zanim zaczniesz poprawiać stany, sprawdź dane.
SELECT p.id_product, p.active AS p_active, ps.active AS shop_active, ps.visibility AS shop_visibility
FROM ps_product p
JOIN ps_product_shop ps ON ps.id_product = p.id_product AND ps.id_shop = 1
WHERE p.id_product = 123;Wartości zmieniamy w obu tabelach, ps_product i ps_product_shop, i zostawiamy visibility = none, żeby produkt nadal nie pokazywał się w katalogu. Po zmianie czyścimy cache Smarty i cache stron.
UPDATE ps_product SET active = 1 WHERE id_product = 123;
UPDATE ps_product_shop SET active = 1 WHERE id_product = 123 AND id_shop = 1;Skrypt PHP uruchamiany w CLI w kontenerze kończył się u nas błędem krytycznym, więc przebieg zamówienia sprawdzamy żądaniami HTTP z sesją, tokenem i nagłówkiem Referer z karty produktu. Test: karta produktu, dodanie do koszyka, dodanie gwarancji, koszyk bez komunikatu, strona zamówienia z kodem 200 i wszystkimi krokami.
Masz ten sam problem? Koszyk wpuszcza produkt, a zamówienie go blokuje? Sprawdzimy dane i kod, zanim zaczniemy zmieniać stany. Opisz sytuację →
Dlaczego klienci dostają mail „brak produktu” przy dostępnym towarze?
Przy okazji wyszła trzecia wada, niezależna od poprzednich. Klient kupił drogi towar i dostał maila o braku produktu, choć towar był na magazynie. Podejrzenie padło na ujemny stan produktu nadrzędnego w panelu, ale to był tylko artefakt: PrestaShop odejmuje stan przy sprzedaży kombinacji, a stanu rodzica nikt nie uzupełniał.
Rzeczywisty łańcuch:
- Moduł tworzył kombinację gwarancji ze stanem 0.
OrderDetail::checkProductStock()odejmuje zamówioną ilość, więc wychodzi −1, a przy włączonym zarządzaniu stanem oznacza to brak towaru.PaymentModule::validateOrder()ustawia status „brak towaru, nieopłacone” i wysyła mail z szablonuoutofstock.
Uwaga: out_of_stock = 1 tego nie blokuje. Ta flaga decyduje tylko o możliwości sprzedaży przy zerowym stanie, a nie o warunku w checkProductStock(). To dwa różne mechanizmy i łatwo je pomylić. W 40 dni zdarzyło się to 14 razy, a poprawka to jedna linia po utworzeniu kombinacji:
StockAvailable::setQuantity((int) $product->id, (int) $idProductAttribute, 1);Jak kombinacje tworzone przy koszyku spowalniają sklep?
Moduł tworzył nową kombinację przy każdym dodaniu gwarancji do koszyka i nie kasował jej po porzuceniu koszyka. Liczba rosła z ruchem, nie ze sprzedażą: na jednym produkcie było 1298 kombinacji. Bije to w hasAttributes() i getAttributesGroups() przy każdym renderze koszyka.
Czyszczenie robimy skryptem, który ma tryb --dry-run i kasuje tylko kombinacje, które jednocześnie:
- nie występują w zamówieniach (743 zostały nietknięte),
- nie leżą w koszyku młodszym niż 30 dni (próg 30 dni dał 544 kasacje, a próg „jakikolwiek koszyk” tylko 199).
Kolejność kasowania to dzieci przed rodzicem, bo PrestaShop nie ma kaskad. Po kasacji kontrolujemy brak osieroconych zamówień i śmieci w tabelach powiązanych, a skrypt uruchamia cron raz w tygodniu. Przed kasacją robimy kopię SQL.
Pułapka z panelem. Otwarcie karty takiego produktu w panelu wieszało panel i sklep na kilka minut: karta wysyłała 64 równoległe żądania z listą kombinacji, a przy sesjach w plikach PHP blokuje plik sesji na czas każdego żądania. Dopóki sesje nie trafią do Redisa, takiego produktu nie otwieramy w panelu i sprawdzamy go SQL-em.
Kiedy zlecić to specjalistom?
Gdy sklep blokuje zamówienia bez widocznej przyczyny, a poprawki stanów nic nie dają. W ASAPDevs zaczynamy od danych i kodu, a potem sprawdzamy całą ścieżkę zamówienia, w ramach optymalizacji PrestaShop i utrzymania sklepu na PrestaShop. O innych awariach tego typu piszemy w artykule o błędzie 500 po wyczyszczeniu cache.
Źródła: dokumentacja deweloperska PrestaShop (klasy Cart, Carrier, OrderDetail); kod PrestaShop 1.7.8 użyty w diagnozie.
