Panel WooCommerce odpowiada na pytanie „ile sprzedałem”. Na pytanie „ile na tym zarobiłem” nie odpowiada wcale, bo nie zna kosztu zakupu. W efekcie najlepszy produkt w zestawieniu to ten z największym przychodem - a to nie to samo co najbardziej opłacalny.
Układ, w którym produkt o przychodzie 40 000 zł zostawia mniej pieniędzy niż produkt o przychodzie 12 000 zł, nie jest rzadki. Jest po prostu niewidoczny.
Najważniejsza decyzja: kiedy zapisać koszt
To jest ta część, którą najłatwiej zrobić źle, bo zły wariant działa i wygląda poprawnie przez pierwszych kilka miesięcy.
Kuszące jest trzymać koszt zakupu przy produkcie i mnożyć go przy liczeniu raportu. Problem pojawia się przy pierwszej podwyżce u dostawcy: koszt się zmienia, a raport przelicza całą historię nową wartością. Marża sprzed roku zmienia się dziś, bo dostawca podniósł cenę. Tego nie da się naprawić wstecz, bo stara wartość już nie istnieje.
Koszt trzeba zamrozić w pozycji zamówienia w chwili zakupu:
add_action( 'woocommerce_new_order_item', function ( $item_id, $item ) {
if ( ! $item instanceof WC_Order_Item_Product ) {
return;
}
$koszt = get_post_meta( $item->get_product_id(), '_koszt_zakupu', true );
if ( '' !== $koszt ) {
wc_add_order_item_meta( $item_id, '_koszt_jedn', (float) $koszt, true );
}
}, 10, 2 );
Dlaczego akurat ten hak
W poradnikach najczęściej spotkasz woocommerce_checkout_create_order_line_item. Działa - ale tylko w klasycznej kasie. Nie odpala przy kasie blokowej, przy zamówieniu dodanym ręcznie w panelu ani przy złożonym przez REST API. Zamówienia telefoniczne i te z integracji wypadają wtedy z raportu po cichu.
woocommerce_new_order_item odpala się we wszystkich tych przypadkach, bo jest niżej - na poziomie zapisu pozycji, a nie konkretnej ścieżki zakupowej.
Skąd czytać dane
Przy kilkuset zamówieniach wszystko policzysz z postmeta i nikt tego nie zauważy. Przy kilku tysiącach raport zaczyna się ładować kilkanaście sekund, a potem przestaje się ładować w ogóle.
WooCommerce ma do tego własne tabele analityczne - wc_order_product_lookup i wc_order_stats - z jedną płaską linią na pozycję zamówienia. Zapytanie łączące je z meta pozycji jest o rząd wielkości szybsze niż to samo liczone z postmeta, bo nie wymaga sklejania kilkunastu wierszy na zamówienie.
Wynik warto trzymać w pamięci podręcznej z krótkim czasem życia i unieważniać go nowym zamówieniem. Raport rentowności nie musi być aktualny co do sekundy; musi się otwierać.

Zwroty liczą się osobno
Zwrot albo anulowanie nie jest ujemną sprzedażą i wrzucanie go do marży zaciera dwie różne informacje. Produkt o dobrej marży i wysokim odsetku zwrotów to zupełnie inny przypadek niż produkt o marży przeciętnej, którego nikt nie odsyła - a po zsumowaniu wyglądają tak samo.
Lepiej trzymać je jako osobną kolumnę obok marży. Wtedy od razu widać, który produkt wymaga poprawienia opisu, a nie ceny.
Koszt reklamy: uczciwe uproszczenie
Przypisanie wydatków reklamowych do konkretnych produktów jest trudne i w sklepie bez porządnej atrybucji po prostu niemożliwe. Rozsądne wyjście to podzielić koszt kampanii proporcjonalnie do przychodu - i napisać w samym raporcie, że to uproszczenie.
Udawanie precyzji, której nie ma, jest gorsze niż jej brak: po pół roku ktoś podejmie na tej podstawie decyzję, nie wiedząc, że liczba była szacunkiem.
Co z tego wynika w praktyce
Pierwsze zestawienie posortowane po marży, a nie po przychodzie, zwykle przestawia kolejność produktów bardziej, niż ktokolwiek się spodziewa. Zdarza się, że produkt uznawany za flagowy utrzymuje się w ofercie wyłącznie dlatego, że nikt nie policzył, ile kosztuje jego obsługa.
Jeśli nie chcesz pisać tego sam, mamy to w Raporcie rentowności - z zamrażaniem kosztu w pozycji zamówienia i zapytaniami na tabelach analitycznych. Jeśli piszesz sam, zacznij od zamrożenia kosztu. Reszta da się dołożyć później, a tego jednego nie da się odzyskać.
Nowa wtyczka? Dowiesz się pierwszy.
Premiery, poradniki i promocje - raz na jakiś czas, bez zasypywania skrzynki.