Trzy rzeczy, które WooCommerce robi z Twoimi kosztami, a o których nikt nie mówi
Koszt zero nie zapisuje się wcale. Zwrot przepisuje zapisany koszt każdej pozycji zamówienia. Wyłączenie funkcji potrafi skasować historię kosztów przy najbliższym zapisie. Wszystkie trzy są prawdziwe, wszystkie trzy siedzą w rdzeniu i wszystkie trzy biorą się z jednej decyzji.
Aktualizacja 2026-09-06
WooCommerce 10.3 wprowadził koszt własny sprzedaży do rdzenia, a razem z nim coś, czego platforma nigdy nie miała: koszt zapisany na pozycji zamówienia, w chwili sprzedaży. To jest ta część, którą warto mieć. Zamówienie z marca nadal mówi, ile towar kosztował w marcu, także po dwóch podwyżkach u dostawcy.
Trzy zachowania wokół tego pola zaskakują ludzi, którzy zaczynają na nim liczyć. Nie są to błędy w sensie „kod robi coś, czego autor nie chciał” — każde ma sensowne uzasadnienie. Są to niespodzianki, a niespodzianka odkryta z maila od klienta kosztuje więcej niż przeczytana wcześniej.
Wszystko poniżej odczytane w źródłach WooCommerce, wersja 11.0.1, we wrześniu 2026. Nazwy funkcji i plików są podane po to, żeby dało się sprawdzić, a nie uwierzyć na słowo.
Jedna linijka kodu tłumaczy wszystkie trzy
Przy zapisie danych pozycji zamówienia koszt przechodzi przez save_cogs_data() w pliku
abstract-wc-order-item-type-data-store.php:
$cogs_value = (float) $cogs_value;
if ( 0.0 === $cogs_value ) {
$this->order_item_data_store->delete_metadata( $item->get_id(), '_cogs_value', '' );
} else {
$this->order_item_data_store->update_metadata( $item->get_id(), '_cogs_value', $cogs_value );
}
Przeczytaj to jeszcze raz z jedną myślą: zero nie jest tu wartością, zero jest kasowaniem.
Zapamiętaj to zdanie. Dwie pozostałe pułapki to ta sama linijka, do której dochodzi się dwiema innymi drogami — jedną przez zwrot, drugą przez pole wyboru w ustawieniach. Kto rozumie ten warunek, przewidzi obie.
Pułapka 1 — koszt zero i brak kosztu to jedno i to samo
Bezpośredni skutek. Produkt, który naprawdę nic Cię nie kosztuje — darmowa próbka, kubek z gadżetów, dodatek do pobrania — zapisuje się na pozycji zamówienia dokładnie tak samo jak produkt, któremu nikt nigdy nie wpisał kosztu: jako nic.
Nie ma flagi, nie ma zera, nie ma znacznika, który powiedziałby, o który z tych dwóch przypadków
chodziło. A produkt bez wpisanego kosztu zwraca 0 z get_cogs_total_value(), więc obie drogi
kończą się tym samym kasowaniem.
Ma to dobrą stronę. Skoro brak naprawdę znaczy nie wiemy, raport może uczciwie powiedzieć, jakiej części przychodu dotyczy znany koszt, zamiast po cichu potraktować niewiadome jako darmowe. Pokrycie jest liczbą prawdziwą, nie oszacowaniem.
Ma też trwałą uciążliwość. Prawdziwe gratisy zostaną na liście braków na zawsze i żadna wtyczka ich z niej nie zdejmie, bo informacja, która by je odróżniła, nigdy nie została zapisana.
Warto znać jedną asymetrię, bo rozstrzyga, jak taka lista w ogóle powinna powstawać. Na produkcie koszt zero jest zapisywany — magazyn danych produktu kasuje tylko puste ciągi i puste tablice, nie zera. Czyli na poziomie produktu zero i brak dają się odróżnić, a na poziomie pozycji zamówienia nie. Lista „produkty bez kosztu” musi więc iść z produktu, a pokrycie z pozycji zamówień. Dwa różne źródła prawdy, z jednego uczciwego powodu. Więcej o zerze i braku.
Pułapka 2 — zwrot przepisuje zapisany koszt całego zamówienia
Ta zmienia liczby, które już zaraportowałeś, i to jest powód, żeby przeczytać tę stronę przed zbudowaniem arkusza na tych danych.
Na końcu wc_create_refund() w wc-order-functions.php:
if ( ...->feature_is_enabled() && $order->has_cogs() ) {
$order->calculate_cogs_total_value(); // <- zamówienie NADRZĘDNE
}
$order->save();
To wywołanie nie ogranicza się do zwracanych pozycji. calculate_cogs_total_value_core()
w abstract-wc-order.php przechodzi po każdej pozycji zamówienia nadrzędnego i woła
$item->calculate_cogs_value(), które czyta koszt z produktu takiego, jaki jest dzisiaj.
Potem save() zapisuje wszystkie wczytane pozycje.
Zwrot jednej pozycji z dwunastopozycyjnego zamówienia przelicza więc zapisany koszt wszystkich dwunastu, z dzisiejszych kosztów produktów. Jeśli cena zakupu zmieniła się w międzyczasie, historia zmienia się razem z nią. Marzec przestaje mówić, ile kosztował marzec.
Teraz połącz to z pułapką 1. Jeżeli koszt produktu został kiedyś wyczyszczony, przeliczenie daje
0, a 0 kasuje. Historia kosztu tego zamówienia nie zostaje przeliczona — ona znika, i nie ma
z czego jej odtworzyć.
Zwrot nie jest przypadkiem brzegowym. Zwrot to wtorek. Jak to wygląda w raporcie.
Pułapka 3 — wyłączenie funkcji potrafi skasować dane przy najbliższym zapisie
Zmierzone na WooCommerce 10.3.0: odznaczenie pola wyboru samo z siebie nie kasuje niczego. Warto to powiedzieć wprost, bo jest odwrotnością tego, czego ludzie się boją.
Ryzykiem jest to, co dzieje się potem. Przy wyłączonej funkcji get_cogs_total_value()
i calculate_cogs_value() zwracają 0 i false. A zero, zgodnie z pułapką 1, kasuje. Każdy
kolejny zapis dotykający pozycji zamówienia — edycja starego zamówienia w panelu, zwrot do niego,
aktualizacja przez REST API — uruchamia przeliczenie, dostaje zero i czyści zapisany koszt tych
pozycji.
Kolejność tych dwóch faktów ma znaczenie. „Wyłączenie jest bezpieczne” i „dotknięcie zamówienia potem już nie” są prawdziwe oba, a użyteczna jest dopiero para. Cała sekwencja.
Dlaczego rdzeń tak robi — po sprawiedliwości
Żadna z tych trzech rzeczy nie jest niechlujstwem.
Kasowanie zer nie pozwala tabeli meta pozycji zamówień zapełnić się wierszami, które nie niosą informacji — a w tabeli rosnącej z każdą pozycją każdego zamówienia to realny argument, nie mikrooptymalizacja.
Przeliczanie przy zwrocie jest spójnym wyborem, jeśli uznać, że po modyfikacji zamówienia jego sumy mają się zgadzać z bieżącymi danymi produktów. Rozwiązanie odwrotne — zwroty zostawiające częściowo nieaktualną sumę kosztu — też da się obronić, ale nie jest oczywiście poprawniejsze.
Zwracanie zera przy wyłączonej funkcji to dokładnie to, co wyłączona funkcja robi wszędzie indziej w WooCommerce. Destrukcyjne staje się dopiero w połączeniu z pierwszą decyzją.
Koszt tych trzech decyzji ląduje w jednym konkretnym miejscu: u każdego, kto traktuje pozycję zamówienia jak niezmienny zapis historyczny. Rdzeń nigdy tego nie obiecał. Po prostu bardzo łatwo to założyć, bo dokładnie tak wygląda snapshot kosztu.
Jeśli budujesz na tych danych
- Traktuj brak wartości jako niewiadomą, nigdy jako zero. Baza nie potrafi powiedzieć, co to było, więc raport zakładający zero wymyśla zysk.
- Pokazuj pokrycie przy każdej liczbie. „Koszt jest znany dla 74% przychodu” to różnica między liczbą, na której można działać, a precyzyjnie wyglądającą liczbą mylącą się po cichu o jedną czwartą.
- Listę braków czytaj z produktu, a pokrycie z pozycji zamówień. Przy każdym innym połączeniu pułapka 1 wyprodukuje fałszywe alarmy.
- Eksportuj co miesiąc. CSV zrobiony na koniec miesiąca to jedyny zapis Twojej historii kosztów, którego zwrot nie przepisze. To najużyteczniejszy nawyk na tej stronie.
- Nie obiecuj niezmiennej historii kosztu, jeśli sam jej nie przechowujesz. Rdzeń jej nie daje, a zbudowanie jej znaczy zapisywanie do sklepu zamiast czytania z niego — inny poziom odpowiedzialności i decyzja do podjęcia świadomie.
Jak to sprawdzić samemu
Wszystko powyższe da się przeczytać w źródłach WooCommerce: save_cogs_data() w
includes/data-stores/abstract-wc-order-item-type-data-store.php, wc_create_refund()
w includes/wc-order-functions.php i calculate_cogs_total_value_core()
w includes/abstracts/abstract-wc-order.php. Wersja 11.0.1, odczytane we wrześniu 2026.
Jeżeli cokolwiek z tego zachowa się inaczej w Twoim sklepie albo w nowszym wydaniu, napisz — te strony są poprawiane, kiedy okazują się nieprawdziwe, a datowana korekta jest dla wszystkich użyteczniejsza niż strona, która po cichu się dezaktualizuje. Jak te same trzy fakty kształtują liczby w naszym raporcie, opisuje jak liczymy zysk.