Wprowadzenie
Wtyczka do Medusy v2, która przelicza ceny wariantów na USD i EUR z ceny w PLN po średnim kursie z tabeli A NBP w chwili zmiany tej ceny i nigdy nie nadpisuje ceny wpisanej ręcznie.
Sprzedajesz w złotówkach, a na wystawie mają stać jeszcze dolary i euro.
@zanreal/medusa-fx-pricing bierze na siebie dokładnie tę jedną rzecz: czyta
cenę PLN wariantu, sprowadza ją do kwoty netto, jeśli ta cena jest zapisana jako
brutto (domyślnie tak jest - zobacz Ustawienia), przelicza ją
po średnim kursie z tabeli A NBP, dokłada marżę, którą wybrałeś, i zapisuje wynik
jako cenę tego wariantu w walucie docelowej. Robi to w chwili, w której cena w
PLN się zmienia, i jeszcze raz każdej nocy, bo kurs rusza się także wtedy, gdy
Twoje ceny stoją.
Obietnica, od której wszystko się zaczyna
Jeśli cenę w USD albo EUR ustawił człowiek, wtyczka jej nie ruszy. Nigdy, ani tego dnia, ani za pół roku. Nie „raczej nie ruszy” i nie „nie ruszy, dopóki różnica jest mała”: kod sprawdza to przed każdym zapisem i przy najmniejszej wątpliwości wycofuje się z decyzją „to nie moje”.
To nie jest dodatek do funkcjonalności, tylko warunek, żeby taka wtyczka w ogóle nadawała się do włączenia na żywym sklepie. Mechanizm jest na tyle nieoczywisty, że ma własną stronę: Ręczne nadpisania.
Czego brakuje w samej Medusie
Moduł Pricing bez problemu przechowa cenę w USD obok ceny w PLN. Nie ma natomiast żadnego zdania na temat tego, skąd ta cena w USD się wzięła, ani niczego, co by ją utrzymywało w zgodzie z kursem.
W praktyce zostają dwa wyjścia. Można wpisywać ceny ręcznie, co jest w porządku w dniu wpisania i coraz mniej w porządku każdego kolejnego dnia, w miarę jak kurs odjeżdża, a nikt tego nie zauważa. Albo można napisać skrypt, który potem każdy taki sklep pisze jeszcze raz, trochę inaczej, i utrzymuje sam.
Ta wtyczka jest tym skryptem, z rozwiązanymi dwoma trudnymi miejscami: co zrobić w dniu, w którym NBP nie opublikowało tabeli, i skąd wiedzieć, których cen nie wolno dotykać.
Wzór
net_pln_amount = sourcePriceIncludesVat ? pln_amount / (1 + vatRate) : pln_amount
foreign_amount = net_pln_amount / nbp_rate * margin_multipliernbp_rate to liczba złotych za jedną jednostkę waluty obcej, bo tak zapisuje to
NBP - dlatego dzielenie przeprowadza kwotę na walutę obcą po surowym kursie
średnim. margin_multiplier dokłada do tego narzut: 1,25 to 25% na wierzchu,
1 oznacza „chcę goły kurs średni i nic więcej”. Krok VAT wykonuje się jako
pierwszy i tylko wtedy, gdy sourcePriceIncludesVat jest true - domyślnie tak
jest, zgodnie ze sklepem, dla którego powstała ta wtyczka, gdzie domyślna cena
PLN jest brutto, a domyślne ceny EUR/USD są netto. Zobacz
Ustawienia.
Wynik jest zaokrąglany w górę przy połówce, do dwóch miejsc po przecinku. Jeżeli którykolwiek składnik nie pozwala policzyć sensownej ceny (kwota PLN równa zeru, kurs niedodatni, marża niedodatnia), funkcja nie zwraca nic, a wariant zostaje pominięty w tym przebiegu. Ta sama zasada wraca w całej wtyczce w najostrzejszej postaci przy marży: domyślnej marży nie ma i nie będzie. Szczegóły w Ustawieniach.
Przebieg krok po kroku
przełącznik włączony? -> nie -> wpis w logu „skipped (disabled)”, zero zapisów
|
tak
v
marża ustawiona? -> nie -> odmowa całego przebiegu, powód zapisany
|
tak
v
odczyt walut obsługiwanych przez sklep + wszystkich wariantów z cenami
|
v
dla usd, potem eur:
waluta włączona w sklepie? -> nie -> pomiń tę walutę
pobranie kursu z tabeli A NBP -> błąd -> pomiń tę walutę
kurs starszy niż tolerancja? -> tak -> pomiń tę walutę
|
v
dla każdego wariantu z domyślną ceną w PLN:
policz kwotę docelową
zdecyduj: utwórz / zaktualizuj / zostaw -> patrz Ręczne nadpisania
|
v
zapis, ponowny odczyt, ostemplowanie tego, co zapisano
|
v
zapis podsumowania -> widoczne w Ustawienia > Ceny walutoweDwie rzeczy w tym schemacie warto powiedzieć wprost.
Walutę pomija się w całości, nigdy wariant po wariancie. Jeśli EUR nie jest włączone w sklepie, kurs EUR nie daje się pobrać albo najnowszy opublikowany kurs jest starszy niż Twoja tolerancja, wtyczka nie podejmuje ani jednej próby zapisu w EUR. Zapisuje powód i przechodzi do następnej waluty. Katalog wyceniony w połowie jest gorszy niż niewyceniony wcale.
Czytana i zapisywana jest wyłącznie cena domyślna. Cena zawężona regułą, czyli nadpisanie dla regionu, cena dla grupy klientów, próg ilościowy albo cokolwiek z cennika, to czyjaś świadoma decyzja i wtyczka trzyma się od niej z daleka. Rusza dokładnie tę cenę, którą widzisz w podstawowej siatce cen na stronie produktu.
Kiedy przebieg się uruchamia
Uruchamiają go trzy rzeczy i wszystkie trzy wykonują ten sam kod, z tymi samymi regułami.
Zmieniła się cena w PLN. Subskrybent nasłuchuje zdarzeń produktu, wariantu i ceny, które Medusa wysyła przy zapisie ceny, ustala, których wariantów to dotyczy, i przelicza wyłącznie je - w ciągu kilku sekund. Zakładasz produkt o 9:00 i o 9:00 ma on ceny w USD i EUR, a nie dopiero o 3:00 następnego dnia.
Na własnych zapisach wtyczka nie reaguje. Zdarzenie o cenie jest rozwiązywane z powrotem do wiersza ceny i brane pod uwagę tylko wtedy, gdy ten wiersz jest w PLN; wtyczka zapisuje wyłącznie USD i EUR, więc jej własny wynik odpada, zanim cokolwiek zostanie zaplanowane. Seria zdarzeń - zapis wariantu z dziewięcioma cenami, import z pliku - jest zbierana i przeliczana raz, a nie raz na zdarzenie, żeby import nie zamienił się w tysiąc zapytań do NBP.
Codzienne zadanie, o 3:00. Pełny przebieg po całym katalogu i zabezpieczenie na wszystko, czego żadne zdarzenie nie powie: opublikowany kurs się zmienił, choć Twoje ceny nie, cenę zapisał surowy SQL, zdarzenie przepadło przy restarcie. To także jedyny przebieg, którego podsumowanie trafia na stronę ustawień jako „ostatni przebieg” - przeliczenie dwóch wariantów po zapisie jednego produktu melduje się w logu, zamiast nadpisywać obraz całego katalogu.
Nacisnąłeś Przelicz teraz. Pełny przebieg, natychmiast, z poziomu Ustawienia > Ceny walutowe.
Instalacja
Tego pakietu nie ma jeszcze na npmie. Instaluje się go jako zależność gitową, przypiętą do konkretnego commitu:
{
"dependencies": {
"@zanreal/medusa-fx-pricing": "github:zanreal-labs/medusa-fx-pricing#5f00ff7801972c1fb757d58e3da98733f5bd3b7d"
}
}Przypnij commit, na którym testowałeś. Opublikowanego tagu jeszcze nie ma, więc
#main przesunąłby Ci się pod ręką przy najbliższym pushu; przypięty commit jutro
znaczy to samo co dziś.
Pakiet buduje się sam przy instalacji: prepare uruchamia medusa plugin:build,
co zamienia pobrane źródła w katalog .medusa/server, na który wskazuje pole
exports. pnpm w wersji 10 i nowszej domyślnie nie uruchamia takiego skryptu dla
zależności, której jeszcze nie ufa, więc świeża instalacja wymaga jednorazowej
zgody:
allowBuilds:
"@zanreal/medusa-fx-pricing@https://codeload.github.com/zanreal-labs/medusa-fx-pricing/tar.gz/5f00ff7801972c1fb757d58e3da98733f5bd3b7d": trueKluczem jest dokładny adres archiwum, na który pnpm rozwiązuje przypięty commit, dlatego niesie ten sam SHA co linia zależności. Przesuwając pin, zmień oba miejsca naraz. Następnie zarejestruj wtyczkę:
module.exports = defineConfig({
plugins: [
{
resolve: "@zanreal/medusa-fx-pricing",
options: {
marginMultiplier: 1.25,
},
},
],
});Każda opcja jest nieobowiązkowa, łącznie z tą - instalacja, która nie ustawia niczego, konfiguruje się w całości z panelu. Na koniec uruchom migracje:
npx medusa db:migrateZaraz po instalacji nic się nie dzieje
I bardzo dobrze. Świeża instalacja jest bezczynna: enabled startuje jako
false, a dopóki przełącznik jest wyłączony, ani subskrybent, ani codzienne
zadanie, ani przycisk ręcznego przeliczenia nie robią nic i mówią o tym
wprost. Wtyczka, która
przecenia żywy katalog w chwili instalacji, to wtyczka, której nikt nie odważy się
wypróbować.
Wejdź w Ustawienia > Ceny walutowe, ustaw marżę, przestaw przełącznik i naciśnij Przelicz teraz, żeby zobaczyć cały przebieg na własne oczy. Podsumowanie pokaże dla każdej waluty, ile cen wtyczka zamierzała zapisać i ile naprawdę zapisała, ile było już poprawnych i ile zostawiono w spokoju, bo należą do człowieka. Przebieg, który nie zapisał niczego, mówi to w tym samym miejscu, zamiast wyglądać jak przebieg udany.
Wymagania
Node.js 22.13 lub nowszy oraz Medusa 2.18.0. Pakiety @medusajs/admin-sdk,
@medusajs/framework, @medusajs/icons, @medusajs/js-sdk, @medusajs/medusa,
@medusajs/ui i react-i18next są zależnościami równorzędnymi, a projekt na
Medusie ma je już u siebie. Panel jest przetłumaczony na angielski i polski, pod
przestrzenią kluczy fxPricing.*.
Co dalej
- Kursy i dni bez kursu - który endpoint NBP jest odpytywany i dlaczego weekend nie wymaga żadnego wyjątku w kodzie, co naprawdę mierzy tolerancja nieaktualności i co się dzieje z walutą niewłączoną w sklepie.
- Ręczne nadpisania - serce wtyczki. Dlaczego znacznika własności nie da się trzymać przy samej cenie, co zapisuje stempel i jakie cztery rozstrzygnięcia są możliwe.
- Ustawienia i konfiguracja - każda opcja, miejsca, w których można ją nadpisać, dwie zmienne środowiskowe, strona w panelu, dwie trasy API oraz obsługa VAT/netto-brutto dla źródłowej ceny w PLN. Zawiera też suchy przebieg do odczytu, który raportuje „bieżące PLN -> proponowane EUR/USD” bez zapisywania czegokolwiek.