Aktualizacja instancji
Instancja nie zawiera kopii platformy: platforma, powłoka panelu administracyjnego i każdy moduł
to pakiety, od których zależy. Aktualizacja polega na przeniesieniu wszystkich pakietów
wydania razem na nową wersję, zainstalowaniu ich i uruchomieniu własnego setup instancji —
który generuje pliki, buduje, wykonuje migracje i instaluje każdy moduł zadeklarowany
w instancji. Moduł nowy w wydaniu nie jest jednym z nich, dopóki go nie zadeklarujesz: zobacz
Dodawanie modułu, który jest nowy w wydaniu.
Robi to jedno polecenie:
pnpm run upgrade # do najnowszego wydania
pnpm run upgrade 0.102.0 # do wskazanego wydania
Uruchom je w katalogu głównym instancji. Jeśli instancję utworzyło wydanie, którego CLI nie ma
polecenia upgrade, zobacz Instancje utworzone przed tym poleceniem.
Nie używaj pnpm update
pnpm update wygląda na oczywisty wybór, a zostawia instancję w stanie, który działa — do czasu —
i kończy się kodem 0:
- Twój
package.jsonprzypina pakiety wydania do dokładnych wersji, żeby drzewo instalowało to samo każdego dnia i tylko jedną kopię każdego pakietu.pnpm updatenigdy nie przesuwa dokładnego przypięcia, więc w instancji utworzonej przez wydanie późniejsze niż0.101.xnie zmienia żadnego z nich. W instancji utworzonej przez0.101.xlub starsze dokładny jest tylko@endora-commerce/contracts:pnpm updateprzesuwa wszystkie pozostałe pakiety, a ten zostawia, więc instalują się dwie kopie i pnpm wypisuje ostrzeżenie unmet peer dla każdego modułu. - Przed
1.0.0zakres^0.101.0kończy się poniżej0.102.0, więcpnpm updatew ogóle nie sięga następnego wydania minor. - Przepisuje zakresy Twoich pozostałych zależności —
"react": "^19"staje się"^19.3.0"— i nigdy nie przesuwa pakietu, który pnpm zainstalował sam jako peer, na przykład@endora-commerce/page-builder-core.
Zanim zaczniesz
- Zatwierdź (commit) bieżący stan, żeby aktualizacja była jednym diffem do przeczytania
i cofnięcia:
package.jsonkażdego członka,pnpm-lock.yamli te same dwa pliki storefrontu. - Zrób kopię zapasową bazy danych.
setupwykonuje migracje nowego wydania, a migracje nie działają wstecz. Z tego samego powodu polecenie odrzuca wersję starszą niż zainstalowana. - Zatrzymaj API i jego workery (konsumentów kolejek), żeby nic nie obsługiwało żądań w czasie zmiany schematu.
- Podejrzyj wynik poleceniem
pnpm run upgrade --dry-run: każdy zakres, który by przesunęło, każdy wpis pliku blokady, który by usunęło, i każde polecenie, które by uruchomiło. Niczego nie zapisuje i niczego nie uruchamia.
Co robi
- Najpierw wszystko sprawdza: że działa w instancji, że platforma jest zainstalowana, że wersja istnieje i że każdy pakiet wydania zadeklarowany w Twoich manifestach jest opublikowany w tej wersji. Każdy problem trafia do jednej odmowy i nic nie zostaje zapisane.
- W każdym
package.jsoninstancji — w katalogu głównym, wbackend/,admin/idocs/— oraz w storefroncie obok niej przesuwa każdy pakiet wydania na nową wersję. Dokładne przypięcie pozostaje dokładne, a^pozostaje^. Żadna inna linia się nie zmienia. - W każdym
pnpm-lock.yamlusuwa wpisy, które wskazują pakiet wydania w innej wersji, żeby pnpm rozwiązał je ponownie — także peery, które zainstalował sam. - Uruchamia
pnpm installipnpm run setupw instancji, a potempnpm installw repozytorium storefrontu. Każde polecenie jest wypisywane, zanim się wykona.
Jeśli instancja jest już w żądanej wersji, polecenie to mówi i niczego nie zmienia.
Czego nie rusza
- Pakietów, które nie należą do wydania. O tym, które należą, decyduje samo wydanie, a nie
zakres
@endora-commerce/, więc moduł wersjonowany osobno, poza wydaniem, i każdy pakiet zewnętrzny zachowują zakres, który wpisałeś. Każdy pominięty pakiet z tego zakresu jest wymieniony w wyniku. - Zależności, która nie jest zakresem wersji —
file:,link:,workspace:, tag. Zostaje wymieniona i zachowana. - Własnych plików storefrontu. Storefront to Twoje repozytorium; przesuwają się tylko jego
pakiety wydania.
--no-storefrontzostawia go całkowicie w spokoju, a--storefront-dir <path>wskazuje storefront, który nie leży obok instancji. Storefront zachowuje więc źródła, z którymi powstał: gdy wydanie zmienia storefront, jaki dostaje nowa instalacja, Twój zyska tę zmianę dopiero wtedy, gdy sam ją przeniesiesz — zmianę z wydania0.103.0opisuje sekcja Bloki modułów w istniejącym storefroncie, a dwie z wydania0.104.0— sekcja Po aktualizacji do wydania 0.104.0, a zmiany z wydania0.105.0— sekcja Po aktualizacji do wydania 0.105.0.
Po zakończeniu
Na koniec polecenie wymienia, co uruchomić ponownie:
pnpm run start # API i jego workery
pnpm run preview:admin # setup zbudował panel od nowa
cd ../my-shop-storefront && pnpm run build && pnpm run start
Zaloguj się i otwórz Modules (/platform/modules), żeby sprawdzić, że lista się wczytuje.
Jeśli po aktualizacji pnpm zgłasza unmet peer dla pakietu zewnętrznego, nowe wydanie podniosło
zakres, który Twój package.json wciąż ma niższy. Podnieś go tam do zakresu podanego
w ostrzeżeniu i uruchom pnpm install.
Dodawanie modułu, który jest nowy w wydaniu
Aktualizacja przesuwa pakiety, które Twoja instancja już deklaruje. Moduł, który pojawia się
w wydaniu po raz pierwszy, do nich nie należy, więc po aktualizacji nie jest ani zainstalowany,
ani widoczny na ekranie Modules — chyba że wymaga go moduł zainstalowany w instancji: wtedy
pierwszy krok setup, endora generate, sam go deklaruje i instaluje. Żeby mieć każdy inny, zadeklaruj jego pakiet w głównym
package.json i uruchom setup — w katalogu głównym instancji:
pnpm add -w -E @endora-commerce/mod-<name>@<version>
pnpm run setup
<version> to wydanie, w którym jest instancja — wersja, jaką @endora-commerce/platform ma
w głównym package.json. Potem uruchom ponownie API i podgląd panelu, jak wyżej: setup wykonał
migracje modułu, zainstalował go i zbudował panel od nowa.
Obie flagi mają znaczenie:
-w— katalog główny instancji jest katalogiem głównym workspace'u, a lista modułów to jegodependencies. Bez tej flagi pnpm 9 odmawia z błędemERR_PNPM_ADDING_TO_ROOT.-Ei wersja — każdy pakiet wydania jest w tym pliku przypięty dokładnie do jednej wersji. Bez nich pnpm zapisuje dla tego jednego pakietu zakres^<version>, a instalacja, która rozwiązuje go od nowa, może wybrać dla niego późniejsze wydanie poprawkowe niż dla reszty: to niespójny zestaw opisany w sekcji Instancja niespójna od początku.pnpm run upgradezachowuje dokładne przypięcie jako dokładne, a^jako^, więc zakres zapisany tutaj pozostaje zakresem.
O tym, czy moduł jest po instalacji włączony, decyduje deklaracja samego modułu; podaje to sekcja wydania, które go wprowadza.
Jeśli krok się nie powiedzie
Polecenie kończy się kodem wyjścia tego kroku i wypisuje, co zostało, jako polecenia do wpisania.
Manifesty wskazują już nową wersję, więc ponowne pnpm run upgrade <version> zaczyna od
instalacji. Samo pnpm run setup zawsze można bezpiecznie uruchomić ponownie.
Instancje utworzone przed tym poleceniem
Polecenie upgrade należy do @endora-commerce/cli. Instancja utworzona przez wydanie 0.101.x
lub starsze ma CLI bez niego i nie ma skryptu upgrade. Zainstaluj CLI wydania, na które
przechodzisz, i uruchom polecenie przez nie:
pnpm add -D -w @endora-commerce/cli@<version>
pnpm exec endora upgrade <version>
Żeby od tej chwili mieć pnpm run upgrade, dodaj "upgrade": "endora upgrade" do scripts
w głównym package.json.
Instancje utworzone w wydaniu 0.100.x
Zmierzone z 0.100.2 do 0.101.1: powyższe polecenia aktualizują instancję i naprawiają to, co
naprawiły pakiety platformy — ekran Modules się wczytuje, a module:enable,
module:disable i module:uninstall działają. Trzech rzeczy nie naprawiają pakiety i aktualizacja
ich nie zmienia:
- Dwóch plików, których instalator
0.100.xnie zapisał — liniiDEPLOYMENTw.envi zależności@endora-commerce/contracts. Zobacz Instancje utworzone w wydaniu 0.100.2 lub wcześniejszym. - Instalator
0.100.xnie zapisywał storefrontu poza klonem repozytorium. Utwórz go poleceniemendora new storefront— zobacz Sklep. - Instalator
0.100.xnie zmieniał zajętego portu, więc zanim cokolwiek uruchomisz, sprawdź, czy.envnie wskazuje bazy danych innego środowiska.
Instancje utworzone przed wydaniem 0.102.0
Wydanie 0.102.0 przeniosło Page Builder z @measured/puck na @puckeditor/core — Puck
zmienił nazwę pakietu w wersji 0.21 — a aktualizacja nie zmienia tej nazwy w Twoich plikach. Gdy
pnpm run upgrade się zakończy, zastąp "@measured/puck" wpisem
"@puckeditor/core": "^0.23.0" w każdym package.json, który go deklaruje (w katalogu głównym
instancji i w storefroncie), a w swoich plikach — w app/, components/ i test/ storefrontu
oraz we wszystkim, co Twoje, w admin/src lub w module nakładkowym (overlay) — zmień importy
'@measured/puck' na '@puckeditor/core' i '@measured/puck/puck.css' na
'@puckeditor/core/puck.css'. Znajdzie je w każdym z drzew
grep -rl "@measured/puck" --exclude-dir=node_modules .. Potem uruchom pnpm install w obu.
Bez tej zmiany pnpm run build storefrontu kończy się błędem sprawdzania typów. Zapisane strony,
bloki i szablony nie wymagają zmian.
Po aktualizacji do wydania 0.103.0
Wydanie 0.103.0 usuwa błąd izolacji tenantów w każdej instancji utworzonej z opublikowanych
pakietów do wydania 0.102.0 włącznie: żądania nie były ograniczane do organizacji klienta ani
klucza API, który je wysyłał, zasięg administratora nie wynikał z jego roli, a wpisy audytu nie
zapisywały administratora, który wykonał operację. Poprawką jest sama aktualizacja. Niczego
w instancji nie trzeba w tym celu edytować — backend/src/index.ts zostaje bez zmian.
Przenieś wszystkie pakiety @endora-commerce/* razem. pnpm run upgrade tak robi. Jeśli
ustawiasz wersje ręcznie, nie zostaw żadnego z tyłu: gdy platforma jest zaktualizowana,
a @endora-commerce/mod-organizations zostaje we wcześniejszej wersji, żaden administrator —
także administrator platformy — nie ma dostępu do żadnej organizacji, a ekrany ograniczone do
organizacji są puste. Backend zapisuje wtedy przy starcie ostrzeżenie, w którym pada nazwa
adminTenantScopePort.
Trzy rzeczy, których aktualizacja nie zrobi za Ciebie.
Przypisz rolę każdemu kontu administratora, które jej nie ma. Uprawnienia administratora
i organizacje, do których ma dostęp, wynikają z roli przypisanej do konta. Konto bez roli było
dotąd traktowane jak konto z dostępem do wszystkich organizacji; od wydania 0.103.0 jest
odrzucane. Nadal może się zalogować i wylogować, a każda trasa panelu chroniona uprawnieniem —
oraz pierwszy odczyt danych organizacji na każdej innej — odpowiada 403 ADMIN_ROLE_REQUIRED.
Aktualizacja nie przypisuje takim kontom żadnej roli, bo każda domyślna oznaczałaby nadanie
dostępu, o którym nikt nie zdecydował. Backend przy każdym starcie zapisuje ostrzeżenie z liczbą
takich kont; brak ostrzeżenia oznacza, że ich nie ma.
Administrator, który może się zalogować, wybiera rolę na ekranie Użytkownicy. Gdy nie może żaden, uruchom w katalogu głównym instancji:
pnpm run admin:create -- --email=<adres e-mail konta> --password-stdin \
--first-name=<imię> --last-name=<nazwisko> [--role=<kod>]
Polecenie znajduje konto po adresie e-mail i je aktualizuje: przypisuje rolę administratora
platformy (platform_admin) albo rolę wskazaną przez --role, i ustawia podane hasło —
dotychczasowe hasło konta przestaje działać. Ustawia też konto jako aktywne, więc nie uruchamiaj
go dla konta, które celowo zdezaktywowano. --password-stdin wczytuje hasło, co najmniej
12 znaków, ze standardowego wejścia. Od tego wydania każda instancja ma rolę platform_admin:
każdy start upewnia się, że istnieje, i nie da się jej już usunąć. Zobacz
Każdy administrator ma rolę.
Jeśli coś Twojego tworzy konta administratorów przez API, musi teraz przesyłać rolę już przy
tworzeniu: POST /api/v1/admin/admin-users bez adminRoleId oraz PATCH, który ustawia je na
null, odpowiadają 400 ADMIN_USER_ROLE_REQUIRED.
Uruchom raz przebieg próbny naprawy zamówień. Wcześniejsze wydanie mogło zostawić zamówienie
anulowane albo opłacone, a mimo to nadal trzymające stan magazynowy lub rezerwację limitu
kredytowego — i nic nie zwalnia ich samo. W katalogu głównym instancji, gdy pnpm run upgrade
się zakończy:
pnpm run cli orders transition-effects-repair # wypisuje, niczego nie zapisuje
pnpm run cli orders transition-effects-repair --apply # zwalnia to, co wymieniła lista
Przeczytaj listę, zanim ją zastosujesz: zwolnienie zmienia liczniki zarezerwowanego stanu
i dostępny limit. --except=<identyfikator zamówienia> pomija zamówienie,
a --order=<identyfikator zamówienia> naprawia tylko wskazane — zobacz
Naprawa zamówień pozostawionych przez wcześniejszą wersję.
Polecenie, które wypisuje No projects matched the filters, niczego nie uruchomiło, niezależnie
od kodu wyjścia: to pnpm --filter backend …, czyli postać dla klonu repozytorium Endora
Commerce, wpisana w instancji.
Dodaj jedną linię do vitest.config.mts storefrontu. Własne testy .tsx storefrontu kończą
się błędem Failed to parse source for import analysis, gdy jego instalacja wybierze Vite 8.
Obok bloku esbuild w tym pliku dodaj:
oxc: { jsx: { runtime: 'automatic', importSource: 'react' } },
Bloki modułów w istniejącym storefroncie
Od wydania 0.103.0 pakiet modułu może zawierać komponenty, które wyświetlają w storefroncie
jego własne bloki Page Buildera, a storefront utworzony przez wydanie 0.103.0 lub nowsze
podłącza je sam. Storefront utworzony wcześniej nie zyskuje tego przez aktualizację:
aktualizacja przesuwa jego pakiety, a nie źródła. Po aktualizacji buduje się i wyświetla strony,
które wyświetlał wcześniej. Czego mu brakuje:
- blok, który wyświetla wyłącznie pakiet modułu, nie jest rysowany — nie ma skryptu
blocks:generate, który znalazłby pakiet, ani niczego, co zaimportowałoby jego komponent; - blok modułu, który wyłączyłeś, nie jest ukrywany, podczas gdy nowszy storefront nie wyświetla w jego miejscu niczego;
- nie ma pliku
lib/page-builder/local-blocks.tsxna blok, który storefront wyświetla sam.
Jeśli nie korzystasz z żadnej z tych rzeczy, możesz zostawić storefront bez zmian. Żeby go
uzupełnić, weź pliki ze storefrontu zapisanego przez nowe CLI. Utwórz go obok swojego — ustawienia
wczyta z pliku .env, który skopiujesz, a nic nie zostanie zainstalowane ani uruchomione:
mkdir ../storefront-0.103.0
cp ../my-shop-storefront/.env ../storefront-0.103.0/.env
pnpm exec endora new storefront ../storefront-0.103.0
Następnie przenieś z tego katalogu do swojego storefrontu:
-
Skopiuj pliki, które są nowe:
components/BlockRenderScope.tsx, kataloglib/page-builder/,scripts/block-discovery.mjs,scripts/generate-blocks.mjs,app/blocks.generated.cssoraz testytest/block-registry.test.tsitest/ssr/module-blocks.test.tsx. -
Weź nową wersję plików, które się zmieniły — a jeśli któryś edytowałeś, nanieś różnicę ręcznie:
components/PageBuilderRender.tsx,app/layout.tsx,app/globals.css,app/blog/_components/BlogCategoryPage.tsx,app/blog/_components/BlogPostBody.tsx,components/Megamenu/MenuCmsBlockEmbed.tsx,lib/api/module-presence.ts,scripts/theme-discovery.mjsoraz testytest/lib/module-presence.test.tsitest/ssr/block-degradation.test.tsx.diff -ru ../my-shop-storefront ../storefront-0.103.0pokazuje każdą różnicę, obok Twoich własnych zmian. -
Dodaj skrypt do
package.jsoni uruchamiaj go wdevibuild, pothemes:generate:"dev": "pnpm run themes:generate && pnpm run blocks:generate && node --env-file-if-exists=.env node_modules/next/dist/bin/next dev","build": "pnpm run themes:generate && pnpm run blocks:generate && next build && pnpm run check:themes","blocks:generate": "node scripts/generate-blocks.mjs", -
Jeśli Twój storefront gdziekolwiek sam montuje komponent
<Render>z Pucka, użyj tamPageBuilderRender: to jedyne miejsce, w którym stosowane są bloki modułów i reguła wyłączonego modułu. -
Uruchom
pnpm run typecheck,pnpm testipnpm run build, a potem usuń katalog, z którego kopiowałeś.
Na ile ta procedura została sprawdzona: na storefroncie utworzonym przez wydanie 0.102.0
i od tego czasu nieedytowanym daje te same pliki, które ma storefront 0.103.0, a sprawdzanie
typów, testy i build przechodzą. Nie sprawdzono jej na storefroncie, którego pliki zmieniono, ani
przez wyświetlenie bloku modułu w storefroncie uzupełnionym w ten sposób — przed wdrożeniem
sprawdź strony, które zawierają treść z Page Buildera.
Po aktualizacji do wydania 0.104.0
pnpm run upgrade 0.104.0 to cała aktualizacja: niczego w instancji nie trzeba w tym celu
edytować. Poniżej: jeden moduł, którego aktualizacja nie dodaje, trzy rzeczy, które po niej
działają inaczej, oraz to, co warto przenieść do istniejącego storefrontu i do własnego kodu.
Nowy moduł, CRM, nie zostaje dodany przez aktualizację. 0.104.0 to pierwsze wydanie modułu
crm (@endora-commerce/mod-crm): szanse sprzedaży z konfigurowalnym przepływem statusów,
tablicą, kalendarzem i analityką, w panelu administracyjnym. Instancja utworzona w wydaniu
0.103.x lub wcześniejszym po aktualizacji go nie ma — nie widać go na ekranie Modules, a
/crm/board odpowiada Page not found. (Instancja utworzona przez instalator wydania 0.104.0
już deklaruje ten pakiet, a pnpm add odpowiada Already up to date.) Żeby go dodać, w katalogu
głównym instancji:
pnpm add -w -E @endora-commerce/mod-crm@0.104.0
pnpm run setup
Jeśli instancja jest już w nowszym wydaniu, wpisz jego wersję zamiast 0.104.0 — obie flagi
opisuje sekcja Dodawanie modułu, który jest nowy w wydaniu. Potem uruchom
ponownie API i podgląd panelu.
- Po instalacji jest włączony.
setupgo instaluje, a od ponownego uruchomienia moduł jest aktywny, z sekcją CRM w menu bocznym. Jeśli chcesz mieć pakiet bez tej funkcji, wyłącz moduł na ekranie Modules (/platform/modules): jego ekrany, uprawnienia i ustawienia znikają, jego trasy odpowiadają503 MODULE_DISABLED, a nic nie zostaje usunięte. - Żadna rola nie dostaje jego uprawnień.
crm:read,crm:write,crm:configureicrm:analyticsnie są automatycznie nadawane żadnej roli. Administrator platformy ma wszystkie uprawnienia i widzi moduł od razu; każdej innej roli, która ma z niego korzystać, nadaj te cztery. - Dane demonstracyjne. W instancji z załadowanym sklepem demonstracyjnym
pnpm run cli demo seeddodaje demonstracyjny lejek sprzedaży.pnpm run cli demo resetwycofuje go niezależnie od tego, czy CRM jest włączony; trzy demonstracyjne etykiety wycofuje tylko reset uruchomiony przy włączonym CRM.
Co robi moduł, opisuje strona CRM.
Trzy rzeczy działają po aktualizacji inaczej. Żadna nie wymaga kroku, chyba że chcesz zachować wcześniejsze zachowanie.
-
Na frazę wyszukiwania odpowiada moduł wyszukiwania.
GET /api/v1/catalog/productsz frazą (q) — to, co czyta strona/searchstorefrontu — było dotąd obsługiwane z bazy danych, chyba że ustawionoCATALOG_SEARCH_BACKEND=meilisearch. Gdy zmienna nie jest ustawiona, fraza trafia teraz do modułusearch, o ile jest włączony: wyniki są uporządkowane według trafności i wybaczają literówkę, a fragment ze środka słowa lub SKU już nie pasuje. Żeby zachować wcześniejsze zachowanie, ustawCATALOG_SEARCH_BACKEND=postgresw pliku.envinstancji. -
Zapytania ofertowego z niewycenioną pozycją nie da się zatwierdzić ani zamienić na zamówienie. Akceptacja, zatwierdzenie i zamiana zapytania ofertowego na zamówienie odpowiadają teraz
409, dopóki któraś pozycja nie ma uzgodnionej ceny jednostkowej. Zapytania, które już ma statusApprovedi taką pozycję, nie da się zamienić na zamówienie; w tym statusie jego pozycji nie można edytować, więc pozostaje złożyć je ponownie (resubmit). Żeby je znaleźć:select distinct qr.id, qr.business_idfrom quote_requests qrjoin quote_request_items it on it.quote_request_id = qr.idwhere qr.status = 'Approved' and it.agreed_unit_price is null;To samo zapytanie z
qr.status = 'Completed'wypisuje zapytania ofertowe, z których w ten sposób już złożono zamówienia; same zamówienia pozostają bez zmian. -
Słownik zawiera każdy język ISO 639-1. Brakujące wiersze są dodawane przy pierwszym starcie po aktualizacji jako nieaktywne, więc nic, co czyta aktywne języki, się nie zmienia;
en-USipl-PLpozostają jedynymi aktywnymi, dopóki nie aktywujesz kolejnego. Dodany wiersz, który usuniesz, wraca przy następnym starcie — zamiast usuwać, zostaw go nieaktywnym.
W istniejącym storefroncie, który zachowuje źródła, z którymi powstał:
- W
lib/api/cms.tszmień tag pamięci podręcznej przy pobraniugetCmsPageIndexz'cms:page'naCMS_STOREFRONT_CACHE_TAGS.pageIndex, importowany z@endora-commerce/contracts. Bez tej zmiany wszystko działa dalej, a nowo opublikowana strona CMS trafia dositemap.xmldopiero po upływie 60-sekundowego okna pamięci podręcznej. - Kategoria może teraz mieć treść z Page Buildera, edytowaną przez akcję Content w drzewie
kategorii. Storefront pokazuje ją tylko wtedy, gdy ją wyświetla. Storefront zapisany przez CLI
wydania
0.104.0to robi:app/(catalog)/c/[slug]/page.tsxwywołujegetCategoryPageContent(node.id, ctx), funkcję zlib/api/catalog.ts, i rysuje wynik komponentemcomponents/CategoryContent.tsx. Żeby je przenieść, utwórz storefront, z którego skopiujesz pliki, tak jak w sekcji Bloki modułów w istniejącym storefroncie.
@dnd-kit/core to nowa zależność peer pakietu @endora-commerce/admin-kit. pnpm sam
instaluje brakujący peer, o ile tego nie wyłączyłeś, więc instancja nie musi nic robić. Jeśli
Twój .npmrc ustawia auto-install-peers=false, dodaj "@dnd-kit/core": "^6.3.1" do
dependencies w admin/package.json, który deklaruje ten pakiet, i uruchom pnpm install.
Jeśli Twój własny kod implementuje port platformy albo buduje jeden z jej rekordów — moduł
nakładkowy albo dubler testowy — cztery kształty z @endora-commerce/contracts zyskały wymagany
element i taki kod nie skompiluje się, dopóki go nie ma: CartRecord.sourceQuoteRequestId
(null, gdy koszyk nie powstał z zapytania ofertowego), CatalogAttributeView.isPriceRule
(false), AuthSessionReadPort.lastSeenByAdminUser i LanguageSeedPort.ensureSeeded.
Na ile zostało to sprawdzone: aktualizacja z 0.103.1, w instancji utworzonej z demonstracyjnym
zestawem modułów, przeszła do końca z pnpm 9 i bez żadnej ręcznej edycji, a panel i niezmieniony
storefront zbudowały się potem i wyświetlały swoje strony; następnie dodano crm poleceniami
pnpm add -w i setup — po instalacji był włączony. Dokładną postać pnpm add podaną wyżej
uruchomiono w pomocniczym workspace'ie, a nie w zaktualizowanej instancji. Nie sprawdzono
wyłączenia crm, obu zmian w storefroncie, instancji z auto-install-peers=false ani instancji
z modułami nakładkowymi; to, co ta sekcja o nich mówi, pochodzi z dzienników zmian wydania.
Po aktualizacji do wydania 0.105.0
pnpm run upgrade 0.105.0 przenosi pakiety i wykonuje migracje wydania, a instancja uruchamia się
potem bez edycji żadnego ze swoich plików. To wydanie zmienia zachowanie: sposób logowania
administratorów i czas życia sesji, kanał sprzedaży, do którego należy zamówienie i metoda, to,
kto odczytuje pole niestandardowe, oraz szereg odpowiedzi API. Ta sekcja mówi, co zrobić i w
jakiej kolejności, a potem — co działa inaczej, osobno dla każdego czytelnika:
operatorów, klientów API i integratorów,
autorów modułów i modułów nakładkowych oraz właściciela
istniejącego storefrontu.
Co zrobić i w jakiej kolejności
Przed aktualizacją:
- Odczytaj
quote_requests.expiry_days— Automatyczne wygaszenie oczekujących po (dni) na karcie Zapytania ofertowe w Ustawieniach — dla domyślnego kanału sprzedaży. Przy0, wartości domyślnej, nic z tego nie wynika. Przy każdej innej wartości zadanie wygaszające, które od tego wydania rzeczywiście działa, w pierwszych przebiegach po aktualizacji wygasza każde otwarte zapytanie ofertowe nieaktywne przez tyle dni. Jeśli chcesz najpierw przejrzeć otwarte zapytania, ustaw wcześniej0; regułę opisuje punkt Zapytania ofertowe wygasają w części Dla operatorów. - W instancji z jednym kanałem sprzedaży odczytaj
orders.min_order_value. Ustawione dla kanału domyślnego, a nie dla wszystkich kanałów, nie było egzekwowane dla zamówień ze storefrontu, a teraz jest. - Jeśli Twoje zadanie uruchamia
demo reset, zdecyduj, czy jego wiersz poleceń potrzebuje--force-delete-financial-records: bez tej flagi reset odmawia teraz wycofania danych demonstracyjnych, w których przyjęto płatność albo wystawiono fakturę.
Po aktualizacji, zanim instancja znów przyjmie ruch:
- Za reverse proxy sprawdź, czy ustawiono
TRUSTED_PROXY_HOPSalboTRUSTED_PROXY_ADDRESSESw środowisku backendu. Żadna z tych zmiennych nie jest nowa. Nowy jest limit błędnych haseł administratora liczony dla adresu klienta: bez jednej z nich każde żądanie ma adres proxy, więc pięć błędnych haseł do jednego konta, wysłanych przez kogokolwiek, opóźnia logowanie na to konto z każdego urządzenia, na którym wcześniej się na nie nie zalogowano. Jak dobrać wartość, mówi punkt G3 listy kontrolnej pierwszego wdrożenia. - W instancji z więcej niż jednym kanałem sprzedaży — a jest nią każda instancja z załadowanymi danymi demonstracyjnymi — przejrzyj każdą metodę dostawy i płatności (Metody dostawy i płatności są udostępniane w wybranych kanałach sprzedaży poniżej) i wprowadź zmiany z części W istniejącym storefroncie.
- Przejrzyj definicje pól niestandardowych na ekranie Pola niestandardowe. Aktualizacja
oznacza każdą istniejącą definicję jako
customer, więc nic, co klienci i integracje dotąd otrzymywali, nie zostaje wycofane. Każde pole, którego wartości są przeznaczone tylko dla administratorów, przestaw nainternal. - Jeśli skrypt albo integracja wywołuje
POST /api/v1/admin/i18n/reloadlubGET /api/v1/admin/i18n/coverage, nadaj roli administratora, jako który się loguje, uprawnieniesettings:writedla pierwszej trasy isettings:readdla drugiej. - Tylko w publicznej instancji demonstracyjnej, która celowo publikuje hasło administratora,
rozważ
ADMIN_AUTH_ACCOUNT_WIDE_LIMIT=off— punkt Logowanie administratora jest ograniczane poniżej mówi, kiedy i co ta zmienna wyłącza. - Zbuduj własne moduły ponownie z tym wydaniem. Źródła, które implementują jeden z portów wymienionych w części Dla autorów modułów i modułów nakładkowych, nie skompilują się, dopóki ich nie zmienisz, a moduł, który tworzy metodę dostawy lub płatności, trzeba wydać ponownie.
W dowolnym momencie:
-
Dodaj trzy wiersze do pliku
.gitignoreinstancji, który zachowuje treść, z jaką powstał:docs/build/docs/.docusaurus/backend/var/Zbudowanie strony dokumentacji instancji zapisuje dwa pierwsze katalogi, a gdy git je śledzi, różnica po aktualizacji to już nie
package.jsonkażdego członka workspace'u i plik blokady — w aktualizacji opisanej na końcu tej sekcji były to 162 pliki. Jeśli któryś z nich jest już w repozytorium, uruchom razgit rm -r --cached docs/build docs/.docusaurus: git nigdy nie ignoruje pliku, który śledzi.backend/var/to miejsce, w którym zapisywane są pliki przesłane do biblioteki zasobów, dopókiassets.local.base_dirma wartość domyślnąvar/assets. Plik.gitignorez żadnego wydania go nie obejmuje, więc katalog pojawia się jako nieśledzony obok różnicy z aktualizacji. To dane Twojego sklepu, a nie źródła: trzymaj go poza repozytorium i twórz jego kopię zapasową razem z bazą danych — baza odtworzona bez niego zawiera zasoby, których plików brakuje. -
W instancji z załadowanym sklepem demonstracyjnym uruchom ponownie
pnpm run cli demo seed. Demonstracyjna rolasales_representativedostała przy tworzeniu kod uprawnienia, którego nie deklaruje żaden moduł, przez co edytor ról odrzuca każdy zapis tej roli odpowiedzią400 Unknown permission(s): organizations:read.assigned; ponowne załadowanie danych wycofuje ten kod z roli i niczego więcej w niej nie zmienia.
Dla operatorów
Logowanie administratora jest ograniczane. Hasło albo kod drugiego składnika dla konta
administratora można próbować tylko kilka razy z rzędu: pięć błędnych prób z jednego adresu na
jedno konto albo dwadzieścia na jedno konto ze wszystkich adresów łącznie rozpoczyna opóźnienie
jednej minuty, które podwaja się z każdą kolejną błędną próbą, najwyżej do piętnastu minut. W
czasie opóźnienia próba otrzymuje odpowiedź 429 ADMIN_AUTHENTICATION_THROTTLED, a poprawne hasło
również jest odrzucane. Nic nie jest blokowane na stałe: licznik zeruje udana próba, a bez niej
jest zapominany trzydzieści minut po pierwszej błędnej.
-
Zakończone logowanie hasłem zostawia na urządzeniu plik cookie
b2b_admin_device. To nie jest sesja i niczego nie daje; urządzenie, które go ma, jest liczone we własnym limicie pięciu prób, a nie w limicie dwudziestu dla konta, więc błędne hasła wysyłane przez kogoś innego nie odcinają administratora od urządzenia, z którego już korzystał. -
Żeby wyzerować wszystkie liczniki jednego konta, w katalogu głównym instancji:
pnpm run cli admin_users unlock --email=<their e-mail>W obrazie produkcyjnym to samo polecenie to
node dist/cli.js admin_users unlock --email=<their e-mail>, uruchomione w kontenerze backendu. Pierwszy wiersz jego wyjścia podaje, na której instancji Redis zadziałało. -
Każde opóźnienie rozpoczęte dla istniejącego konta zapisuje jeden wpis w dzienniku audytu,
admin_user.authentication_throttled. -
Liczniki są przechowywane w Redis. Dopóki Redis nie odpowiada, logowanie jest odrzucane odpowiedzią
503 ADMIN_AUTHENTICATION_UNAVAILABLE. -
Publiczna instancja demonstracyjna. Limit dwudziestu prób zakłada, że hasło jest tajne. W instancji, która celowo publikuje adres e-mail i hasło administratora, każdy odwiedzający jest urządzeniem logującym się po raz pierwszy i korzysta z tego wspólnego limitu. W takiej instancji, i tylko w takiej, ustaw
ADMIN_AUTH_ACCOUNT_WIDE_LIMIT=offw środowisku backendu i uruchom go ponownie. Zmienna wyłącza liczenie błędnych haseł dla całego konta i nic więcej — limit dla adresu, limit dla znanego urządzenia, oba limity kodów drugiego składnika i opóźnienia pozostają — a backend, dopóki limit jest wyłączony, zapisuje ostrzeżenie w logu przy każdym starcie. Działa wyłącznie dokładna wartośćoff. To nie jest ustawienie i nie da się go zmienić w panelu administracyjnym. Plikcompose.prod.ymlzapisany przez wcześniejsze wydanie nie przekazuje tej zmiennej do backendu: dodajADMIN_AUTH_ACCOUNT_WIDE_LIMIT: ${ADMIN_AUTH_ACCOUNT_WIDE_LIMIT}obok wierszaTRUSTED_PROXY_ADDRESSES. Nie ustawiaj jej tam, gdzie hasła administratorów nie są publiczne.
Liczby są stałymi modułu, a nie ustawieniami. Zobacz Ograniczanie powtarzanych błędnych haseł i kodów.
Zmiana danych uwierzytelniających kończy sesje uzyskane przed nią. Spodziewaj się, że administratorzy i klienci zostaną wylogowani na pozostałych urządzeniach tam, gdzie dotąd nie byli:
- administrator, który zmienia własne hasło albo wyłącza własne uwierzytelnianie dwuskładnikowe, kończy wszystkie pozostałe sesje konta i zachowuje tę, z której dokonano zmiany;
- dezaktywacja albo usunięcie administratora kończy wszystkie sesje konta, a sesja konta, które nie jest aktywne, jest odrzucana niezależnie od tego, czy cokolwiek ją unieważniło;
pnpm run admin:createuruchomione ponownie dla istniejącego konta zastępuje jego hasło, jak dotąd, a teraz kończy też wszystkie sesje tego konta;- klient, który zmienia hasło albo wyłącza uwierzytelnianie dwuskładnikowe, kończy wszystkie pozostałe sesje konta; użycie odnośnika resetującego hasło kończy je wszystkie, podobnie jak zresetowanie drugiego składnika klienta przez administratora.
Każda z tych operacji wycofuje też logowanie rozpoczęte i niedokończone — oczekujące wyzwanie drugiego składnika albo bilet konfiguracji. Klucze API nie są sesjami i pozostają bez zmian. Zobacz Sesje a zmiana hasła.
Żeby zmienić własne hasło, administrator podaje obecne. Ekran profilu ma pole Obecne hasło
nad polem Nowe hasło, wymagane tylko wtedy, gdy wpisano nowe hasło, a nowe hasło równe obecnemu
jest odrzucane. Reset hasła innego administratora na ekranie Users oraz admin:create
pozostają bez zmian.
Czas bezczynności do wylogowania obowiązuje każdego administratora.
admin.idle_logout_minutes trafia teraz do każdego zalogowanego administratora. Administrator,
którego rola nie obejmuje settings:read, był wylogowywany po wbudowanych 60 minutach niezależnie
od ustawienia, a teraz jest wylogowywany po skonfigurowanym czasie.
Pole niestandardowe ma odbiorców. Każda definicja mówi, komu zwracane są jej wartości:
internal — tylko administratorom — albo customer — także w odpowiedziach dla klienta
dotyczących jego zamówień i zapytań ofertowych oraz w zewnętrznych (dla klucza API) odpowiedziach
z zamówieniami. Definicje istniejące przed aktualizacją mają customer; pole utworzone od teraz
ma internal, chyba że autor wybierze inaczej w formularzu definicji (Kto widzi wartość).
Zmiana odbiorców działa od następnego odczytu, a do każdego procesu API może dotrzeć z opóźnieniem
do pięciu sekund. Zobacz
Widoczność: kto odczytuje wartości pola.
Zapytania ofertowe wygasają. Zadanie wygaszające, które opisuje dokumentacja modułu
quote_requests, teraz działa: co 30 minut, w każdym procesie obsługującym kolejki, dopóki moduł
jest włączony. quote_requests.expiry_days = 0, wartość domyślna, wyłącza je. W przeciwnym razie
zapytanie, które nadal ma status Pending albo Created from admin, od tylu dni nie dostało
żadnego wpisu w historii i nie zawiera oferty, której termin ważności jeszcze nie minął,
otrzymuje status Expired.
- Włączenie ustawienia albo obniżenie jego wartości wygasza zaległości. Reguła dotyczy wszystkiego, co jest otwarte, a nie liczy się od dnia ustawienia — a instancja, która ma już to ustawienie, trafia na te zaległości w pierwszych przebiegach po tej aktualizacji.
- Jeden przebieg wygasza najwyżej 500 zapytań, od najstarszych; większe zaległości są obsługiwane w kolejnych przebiegach.
- Dla zapytania, które powinno było wygasnąć ponad 24 godziny przed przebiegiem, który do niego
dotarł, nie jest zapisywany rekord powiadomienia. Wpis w historii nadal powstaje, a
rfq.expired.v1nadal jest emitowane. - Zadanie odczytuje wartość domyślnego kanału sprzedaży i stosuje ją do zapytań z każdego kanału;
kanał ustawiony na
0nie jest wyjątkiem.
Zobacz Zadania w tle.
Zamówienie złożone w storefroncie jest zapisywane w kanale sprzedaży, w którym wykonano
żądanie. POST /api/v1/orders brał dotąd kanał zamówienia z opcjonalnego pola salesChannelId
w treści żądania, a gdy go nie było — używał kanału domyślnego; teraz używa kanału rozpoznanego dla
żądania — X-Sales-Channel, ?salesChannel=, mapa hostów, a w ostatniej kolejności kanał
domyślny — i odrzuca treść żądania, która wskazuje inny kanał. Co to oznacza, zależy od liczby
kanałów sprzedaży w instancji. Instancja z załadowanymi danymi demonstracyjnymi ma dwa —
pl_retail, domyślny, oraz pl_b2b_vip — więc dotyczy jej przypadek Więcej niż jeden, tutaj i
przy metodach poniżej.
- Jeden kanał sprzedaży. Zmienić może się jedna rzecz. Jeśli
orders.min_order_valuejest ustawione dla kanału domyślnego, a nie dla wszystkich kanałów, to nie było egzekwowane dla zamówień ze storefrontu, a teraz jest. Jeśli nie masz pewności, który to przypadek, sprawdź wartość w Ustawieniach przed aktualizacją. - Więcej niż jeden. Nowe zamówienia składane w storefroncie kanału innego niż domyślny są
zapisywane w tym kanale, a nie w domyślnym, i obowiązują dla nich jego minimalna wartość
zamówienia, magazyny, ustawienia realizacji, numeracja zamówień, dane sprzedawcy i numeracja
faktur oraz język wiadomości e-mail. Istniejące zamówienia nie są zmieniane. Koszyki nadal
powstają w kanale domyślnym, więc ceny pozycji i promocje takiego zamówienia są nadal z kanału
domyślnego, a każdy produkt został sprawdzony względem kanału żądania, które dodało go do
koszyka, a nie względem kanału zamówienia — zobacz Który kanał sprzedaży zapisuje zamówienie
na stronie modułu
orders.
Metody dostawy i płatności są udostępniane w wybranych kanałach sprzedaży. Na ekranach
/delivery-methods i /payment-methods każda metoda ma teraz pole Kanały sprzedaży, a wybór
jest egzekwowany: storefront pokazuje tylko metody dostępne w kanale, w którym kupuje klient, a
zamówienie z metodą niedostępną w jego kanale jest odrzucane. Metoda nieprzypisana do żadnego
kanału jest dostępna w każdym kanale.
-
Jeden kanał sprzedaży. Nic się nie zmienia: każda metoda jest dostępna w jedynym kanale, niezależnie od tego, czy jest do niego przypisana, czy nie jest przypisana do żadnego.
-
Więcej niż jeden: przejrzyj każdą metodę. Kolumna Kanały sprzedaży na obu ekranach pokazuje stan każdej z nich.
- Przypisane tylko do kanału domyślnego, a więc po aktualizacji niedostępne przy składaniu zamówienia w pozostałych kanałach: każda metoda utworzona dotąd w panelu administracyjnym, bo ekrany nie dawały innej możliwości, oraz każda metoda utworzona przez moduł bramki płatności lub przewoźnika we wcześniejszym wydaniu w instancji, która była już uruchomiona. Nic nie poszerza ich za Ciebie — takiej metody nie da się odróżnić od ograniczonej celowo.
- Nieprzypisane do żadnego kanału, a więc dostępne w każdym kanale: każda metoda tworzona przez moduł od tego wydania, niezależnie od chwili instalacji; metody utworzone przez moduł we wcześniejszym wydaniu podczas pierwszej konfiguracji instancji, przed jej pierwszym uruchomieniem; oraz metody z danych demonstracyjnych.
Otwórz każdą metodę i wybierz jej kanały albo odznacz wszystkie, aby była dostępna wszędzie.
-
Metoda dostawy utworzona w panelu administracyjnym we wcześniejszym wydaniu może nie być oferowana przy składaniu zamówienia w żadnym kanale, niezależnie od liczby kanałów w instancji — i nie była też przed aktualizacją. Ekran nie wysyłał adaptera, więc metoda została zapisana z własnym kodem jako adapterem, a jeśli ten kod nie jest kluczem zarejestrowanego adaptera, nie ma czym jej oferować. To wydanie oznacza taki wiersz jako Nieoferowana przy składaniu zamówienia, wraz z przyczyną. Otwórz metodę i wybierz adapter w nowym polu Adapter — Wysyłka własna (ręczna) dla przesyłek, które nadajesz samodzielnie. Od tej chwili jest oferowana w kanałach, do których jest przypisana, czyli dla metody utworzonej w ten sposób — tylko w kanale domyślnym. Metod płatności to nie dotyczy: nie dało się utworzyć metody z adapterem, który nie jest zarejestrowany.
Przypisywanie kanałów sprzedaży wymaga delivery_methods:write / payment_methods:write i
niczego więcej; uprawnienia kanałów sprzedaży nie są potrzebne.
Listy dozwolonych metod organizacji obowiązują przy każdym sposobie składania zamówienia. Metoda dostawy lub płatności spoza niepustej listy dozwolonych metod organizacji, dla której składane jest zamówienie, jest odrzucana przy składaniu zamówienia i przy jego podglądzie — w storefroncie, przy przyjmowaniu zamówień z kluczem API oraz wtedy, gdy administrator tworzy zamówienie dla klienta. Żadne ustawienie nie zwalnia z tego administratora: formularz tworzenia pokazuje każdą metodę, a podgląd i utworzenie odrzucają tę, której organizacja nie dopuszcza. Pusta lista, jak dotąd, niczego nie ogranicza. Zobacz Listy dozwolonych metod przy składaniu zamówienia.
Adapter metody dostawy wybiera się na ekranie /delivery-methods. Formularz ma wymagane pole
wyboru Adapter. Metoda, której adaptera nie dostarcza żaden włączony moduł, jest oznaczona
jako Nieoferowana przy składaniu zamówienia wraz z przyczyną, a naprawia się ją, otwierając ją
i wybierając zarejestrowany adapter; nic nie jest za Ciebie przepisywane ani usuwane. Adaptera
metody, do której odwołują się przesyłki, nie da się zmienić: ustaw metodę jako nieaktywną i
utwórz drugą dla innego adaptera.
Ekran Webhooks oferuje te typy zdarzeń, które są dostarczane, i żadnych innych. Zapisana
subskrypcja, która wskazuje typ przez nic niedostarczany, zostaje zachowana, jest na ekranie
oznaczona jako niedostarczana i niczego nie otrzymuje, jak dotąd. Dostarczanych jest sześć typów,
których dotąd nie dostarczano: product.created.v1, product.updated.v1,
product.archived.v1, rfq.created.v1, rfq.expired.v1 i credit_limit.adjusted.v1. Nic nie
jest grupowane — subskrypcja product.updated.v1 otrzymuje jedno dostarczenie na każdy produkt
zapisany przez import albo edycję zbiorczą. Zobacz
Które zdarzenia są dostarczane.
Wiadomość e-mail, która została tylko zapisana w logu serwera, nie jest odnotowywana jako
wysłana. W instancji bez SMTP_URL wiersze email_deliveries są zapisywane z
status = 'logged' zamiast 'sent' (wiersze zapisane wcześniej zachowują sent), wystawienie
faktury albo ponowne wysłanie jej wiadomości z panelu administracyjnego informuje, że wiadomości
nie wysłano, bo nie skonfigurowano serwera poczty, a przypomnienie o wydarzeniu CRM jest
odnotowywane jako dostarczone wyłącznie do dzwonka powiadomień.
demo reset to jedna transakcja i odmawia, gdy dane demonstracyjne obejmują dokumenty
finansowe. Albo kończy się w całości, albo niczego nie zmienia. Wycofuje też to, co zostało po
korzystaniu ze sklepu demonstracyjnego w ramach organizacji demonstracyjnej — zamówienia, koszyki,
zapytania ofertowe, adresy i podobne; te wiersze są usuwane, a dane innej organizacji pozostają
nietknięte. Polecenie kończy się kodem 1, zanim cokolwiek usunie, gdy organizacja demonstracyjna
ma płatność opłaconą lub zwróconą, fakturę albo korektę, rekord systemu księgowego albo zwrot, i
wypisuje, ile każdego z nich znalazło. Zamówienia złożone i nigdy nieopłacone się nie liczą. Żeby
usunąć dokumenty finansowe razem z resztą:
pnpm run cli demo reset --force-delete-financial-records
Flaga jest odczytywana wyłącznie z tego wiersza poleceń. Pełną listę tego, co się liczy, podaje strona Pierwsze kroki.
Endpoint stanu podaje wydanie. Pole version w GET /api/v1/_health to wersja pakietu
@endora-commerce/platform załadowanego przez proces — 0.105.0 — albo unknown, podczas gdy
dotąd każda instancja podawała 0.0.0. Monitoring, który porównywał to pole z 0.0.0, albo
wdrożenie, które sterowało nim zmienną npm_package_version, wymaga zmiany. Panel administracyjny
pokazuje to samo wydanie jako znaczek pod logotypem w menu bocznym. Zobacz
Pole version.
W dzienniku audytu przybywa wpisów i ubywa szumu. Każdy zapis bloku, szablonu albo hooka CMS
wykonany w panelu zostawia teraz wpis w dzienniku audytu (cms_block.*, cms_template.*,
cms_hook.*), tak jak dotąd zapisy stron. Zmiana własnego hasła przez administratora jest
zapisywana jako admin_user.change_password z via: 'self_service', a nie jako
admin_user.update. A cztery zadania cykliczne — domykanie następstw zamówień, przypomnienia o
wydarzeniach CRM, porządkowanie przebiegów plików produktowych i przegląd statusów cenników —
nie zapisują już wiersza tenant.escape_hatch w przebiegu, który nie ma nic do zrobienia, co w
spokojnej instancji dawało kilka tysięcy wierszy dziennie.
Trzy drobniejsze rzeczy: administrator, którego konto nie ma roli, widzi o tym komunikat w panelu administracyjnym; karty szansy sprzedaży w CRM pokazują liczniki, a migracja, która dodaje liczniki nieprzeczytanych wiadomości, oznacza każdą wiadomość napisaną przed nią jako przeczytaną przez wszystkich; a kampania newslettera bez kanału sprzedaży jest podglądana i wysyłana z identyfikacją wizualną kanału domyślnego.
Dla klientów API i integratorów
Uwierzytelnianie i autoryzacja:
- Zabezpieczenie trasy odpowiada przed walidacją treści żądania. Na każdej trasie, która
deklaruje zabezpieczenie sesją, uprawnieniem albo kluczem API, żądanie bez ważnych danych
uwierzytelniających otrzymuje
401, a bez uprawnienia403, niezależnie od treści. Klient, który dla niepoprawnej treści wysłanej bez danych uwierzytelniających oczekiwał400 VALIDATION_FAILED, zobaczy401/403. Uprawniony klient z niepoprawną treścią otrzymuje to samo400co dotąd, a treść odrzucana przez sam parser — błędny JSON, nieobsługiwany typ mediów, treść ponad limit — nadal otrzymuje odpowiedź jako pierwsza. PATCH /api/v1/admin/mewymagacurrentPassworddo zmiany hasła.passwordbez tego pola to400 VALIDATION_FAILED; błędne —403 CURRENT_PASSWORD_INVALID; nowe hasło równe obecnemu —400 NEW_PASSWORD_UNCHANGED. Odrzucone żądanie niczego nie zmienia, także imienia i nazwiska wysłanych razem z nim. Żądanie bezpassworddziała jak dotąd.- Trasy danych uwierzytelniających administratora mogą odpowiedzieć
429 ADMIN_AUTHENTICATION_THROTTLED, z nagłówkiemRetry-Afteri tą samą liczbą werror.details.retryAfterSeconds, albo503 ADMIN_AUTHENTICATION_UNAVAILABLE:POST /api/v1/auth/admin/login, obecne hasło wPATCH /api/v1/admin/me,POST /api/v1/auth/admin/mfa/verify,POST /api/v1/admin/account/mfa/disableiPOST /api/v1/admin/account/mfa/recovery-codes/regenerate. Trasy klientów pozostają bez zmian. - Jedno wyzwanie drugiego składnika dopuszcza pięć kodów, dla klientów i administratorów,
niezależnie od tego, jak są wysyłane; kolejny kod to
429 MFA_TOO_MANY_ATTEMPTS, a logowanie zaczyna się od nowa. - Sesja konta administratora, które jest zdezaktywowane albo usunięte, otrzymuje
401 UNAUTHORIZEDna każdej trasie panelu; trasa z kodem uprawnienia odpowiadała jej403 FORBIDDEN. Aktywne konto bez uprawnienia nadal otrzymuje403. - Przy logowaniu klienta
403 ACCOUNT_BLOCKEDpojawia się tylko wtedy, gdy hasło jest poprawne. Błędne hasło do zablokowanego konta to401 INVALID_CREDENTIALS. POST /api/v1/admin/i18n/reloadwymagasettings:write, aGET /api/v1/admin/i18n/coveragewymagasettings:read; sesja bez tego kodu otrzymuje403.- Subskrypcje push należą do tego, kto je utworzył.
DELETE /api/v1/storefront/pwa/subscriptionsusuwa subskrypcję tylko wtedy, gdy żądanie zawiera jej własne klucze —{ endpoint, keys: { p256dh, auth } }— albo sesję klienta, do którego należy; w obu przypadkach odpowiada204.POSTna tę samą ścieżkę aktualizuje już zarejestrowany endpoint na podstawie tego samego dowodu, a w przeciwnym razie odpowiada201z nowymidi niczego nie zapisuje.
Zamówienia, metody i zapytania ofertowe:
POST /api/v1/orderszapisuje kanał rozpoznany dla żądania.salesChannelIdw treści, które wskazuje inny kanał, to422 VALIDATION_FAILEDzdetails.code = "order_sales_channel_mismatch". Przestań wysyłać to pole i podaj kanał wX-Sales-Channel.- Składanie zamówienia i jego podgląd odrzucają metodę, której nie dopuszcza kanał zamówienia
albo organizacja —
400 VALIDATION_FAILEDzerror.details.coderównymdelivery_method_not_in_sales_channel,payment_method_not_in_sales_channel,delivery_method_not_allowed_for_organizationalbopayment_method_not_allowed_for_organization, naPOST /api/v1/orders,POST /api/v1/orders/preview-total,POST /api/v1/admin/orders,POST /api/v1/admin/orders/previewiPOST /api/v1/external/orders. Na ostatniej z nich odmowa z powodu listy dozwolonych metod już istniała; jejerror.messagekończy się teraz "… is not available to this Organization.",error.detailsjest obecne, a gdy obie metody są spoza list, wskazywana jest metoda dostawy. Dopasowuj poerror.details.code. GET /api/v1/delivery-methodsiGET /api/v1/payment-methodszwracają metody kanału rozpoznanego dla żądania. Klient, który nie wysyłaX-Sales-Channel, otrzymuje odpowiedź dla kanału domyślnego. Zakup jednym kliknięciem działa tak samo:GET /api/v1/quick-order/one-click/eligibilityodpowiada{ enabled: false, reason: "missing_defaults" }, gdy domyślna metoda kupującego nie jest dostępna w kanale żądania.salesChannelIdswPUT /api/v1/admin/{delivery,payment}-methods/:code: pominięte — zostawia przypisanie bez zmian (a nową metodę przypisuje do kanału domyślnego), jak dotąd;[]usuwa teraz wszystkie przypisania, udostępniając metodę w każdym kanale, podczas gdy dotąd było ignorowane; identyfikator, który nie wskazuje żadnego kanału sprzedaży, to400 VALIDATION_FAILED, a nie500.PUT /api/v1/admin/delivery-methods/:codeodpowiada409na zmianęadaptermetody, do której odwołują się przesyłki. WierszeGET /api/v1/admin/delivery-methodszawierająavailability: { ownerModule, available, ownerPresence }, aGET /api/v1/admin/delivery-methods/adapterswymienia adaptery do wyboru. Utworzenie metody bezadapternadal przyjmuje kod metody jako adapter: wysyłaj to pole i odczytujavailability.availablez odpowiedzi.- Odpowiedzi z zamówieniami. Odpowiedzi dla klienta i zewnętrzne zawierają
placedOnBehalf: booleani nie zawierają jużplacedOnBehalfByAdminUserId; odpowiedzi dla panelu zawierają oba pola. Zastąporder.placedOnBehalfByAdminUserId !== nullprzezorder.placedOnBehalf.customFieldValuesw tych odpowiedziach zawiera tylko pola, których odbiorcą jestcustomer. - Odpowiedzi dla klienta nie zawierają identyfikatorów administratorów. Odpowiedzi z
zapytaniami ofertowymi dla klienta nie zawierają już
createdByAdminUserIdiassignedAdminUserId, a ichevents[]—actorAdminUserId(actorRoleLabelnadal mówi, kto działał); odpowiedzi z komentarzami do zamówienia dla klienta nie zawierają jużauthorAdminUserId— komentarz, któregoauthorCustomerAccountIdma wartośćnull, napisał pracownik. Odpowiedzi dla panelu pozostają bez zmian. - Z dwóch równoczesnych przejść jednego zapytania ofertowego wygrywa jedno. Akceptacja albo
odrzucenie przez kupującego, edycja przez klienta oraz zatwierdzenie, rewizja, anulowanie albo
przypisanie przez sprzedawcę, które przegrywają z innym przejściem albo z zadaniem
wygaszającym, otrzymują
409 VERSION_CONFLICT.
Pola niestandardowe, webhooki i zdarzenia:
POST /api/v1/admin/custom-fields/definitionsbezaudiencetworzy poleinternal. Wyślij"audience": "customer"dla pola, które mają odczytywać klienci i integracje. Ponowne utworzenie, jakocustomer, usuniętego klucza, który nadal ma zapisane wartości, to409 CUSTOM_FIELD_DEFINITION_INVALID: utwórz pole jakointernal, a potem zmień odbiorców.PATCH …/definitions/:idprzyjmujeaudiencei nie zeruje jużconfig, gdy treść go nie wymienia.eventTypeswebhooka są walidowane.POST /api/v1/admin/webhooksiPATCH /api/v1/admin/webhooks/:idodpowiadają422 WEBHOOK_EVENT_TYPE_NOT_DELIVERABLE, z odrzuconymi nazwami werror.details.eventTypes, dla typu, który nie jest ani wbudowany, ani wniesiony przez włączony moduł. Sprawdzana jest tylko nazwa, którą zapis dodaje; zapisana subskrypcja zachowuje nazwy, które ma. Dostarczane w tym wydaniu:order.created.v1iorder.status_changed.v1;crm.opportunity.created.v1,crm.opportunity.status_changed.v1icrm.opportunity.closed.v1, dopókicrmjest włączony; oraz sześć nowych, dopóki ich moduł jest włączony —product.created.v1,product.updated.v1,product.archived.v1,rfq.created.v1,rfq.expired.v1icredit_limit.adjusted.v1.GET /api/v1/admin/webhooks/event-typeszwraca typy wniesione przez moduły. Zdarzenia produktów trafiają wyłącznie do subskrypcji ogólnoplatformowych.- Wniesiony typ zdarzenia jest dostarczany tylko wtedy, gdy jego moduł jest włączony; zapisane subskrypcje zostają zachowane i w tym czasie niczego nie otrzymują.
order.created.v1jest ogłaszane po zatwierdzeniu transakcji zamówienia, więc nieudane złożenie zamówienia niczego nie ogłasza.rfq.expired.v1zyskujeorganizationIdi występuje tylko tam, gdzie działa zadanie wygaszające.product.archived.v1jest teraz emitowane zawsze, gdy status produktu zmienia się nainactive.
Kody błędów i drobniejsze zmiany:
- Dwie odmowy dotyczące pozycji koszyka mają własny kod zamiast
VALIDATION_FAILED:400 CART_PRODUCT_QUOTE_ONLY(details.productId), którym pozaPOST /api/v1/cart/itemsmogą odpowiedzieć także szybkie zamówienie, zakup jednym kliknięciem, dodanie listy zakupów do koszyka, tworzenie zamówienia w panelu i przyjmowanie zamówień z kluczem API; oraz422 CART_QUANTITY_INVALID, do którego nie prowadzi żadna trasa HTTP — otrzymuje go moduł wywołującyCartWritePort.addItemw procesie. detailszyskało pola, przy niezmienionym kodzie i statusie:409 LIMIT_INSUFFICIENT—availableAmount,orderTotal(napisy z dwoma miejscami po przecinku) icurrency;409 STOCK_UNAVAILABLE—productId,sku,productNameirequestedQuantity;413 ASSET_UPLOAD_TOO_LARGE—maxFileSizeMb;403 API_KEY_OUT_OF_SCOPE—requiredScope. Klient, który porównywałerror.message, zobaczy dłuższe zdania, teraz także po polsku.detailszmieniło kształt w400 SETTING_VALUE_SHAPE_MISMATCH. To obiekt zdetails.code—wrong_typealbonot_an_option— orazsettingCode; tablica{ path, issue }, którą odmowa z powodu typu zwracała jako samodetails, to terazdetails.issues, a odmowa z powodu opcji zawieraallowedValuesienumOptions.- Niepoprawny identyfikator to
404, a nie500, na każdej trasie/api/v1/admin/customers/:id…(CUSTOMER_NOT_FOUND) i na samoobsługowych trasach adresów (CUSTOMER_ADDRESS_NOT_FOUND). GET /api/v1/catalog/productswycina stronę z produktów, które pasują. Na domyślnej ścieżce listy strona zawieralimitpasujących produktów, o ile tyle istnieje, ahasMorema wartośćtruetylko wtedy, gdy istnieje kolejny.filter[category]wskazujące kategorię, która nie istnieje, zwraca jedną pustą stronę zhasMore: false. Kształt odpowiedzi i format kursora pozostają bez zmian.POST /api/v1/organizations/registerodpowiadaemailVerificationSent: falsew instancji bez serwera poczty, w której wiadomość została tylko zapisana w logu.- Nowe elementy:
idleLogoutMinuteswGET /api/v1/admin/me;GET /api/v1/admin/platform-info({ "version": string | null }, dla każdego zalogowanego administratora);noteCount,attachmentCountiunreadMessageCountw szansie sprzedaży CRM orazPOST /api/v1/admin/crm/opportunities/:id/messages/read.
Dla autorów modułów i modułów nakładkowych
Zabezpieczenie zadeklarowane na trasie działa przed walidacją. Łańcuch preHandler, który
trasa deklaruje we własnych opcjach, jest przy rejestracji trasy przenoszony do jej
preValidation; żadne miejsce wywołania się nie zmienia, a moduł zbudowany z wcześniejszą
platformą jest objęty bez ponownego budowania. Dwie rzeczy do sprawdzenia we własnym kodzie:
- Funkcja przekazana jako
preHandlertrasy widzirequest.bodysparsowane, ale niezwalidowane — dowolną wartość JSON — orazrequest.params/request.querybez konwersji i wartości domyślnych ze schematu. Praca wymagająca zwalidowanego żądania należy do handlera, do interceptora API albo dopreHandlerdodanego przezaddHookw zasięgu Twojej wtyczki. addHook('preHandler')w zasięgu wtyczki działa teraz po zabezpieczeniach trasy. To, co zabezpieczenie trasy odczytuje z żądania, musi się tam znaleźć wonRequestalbopreValidation.
Zobacz Kiedy działa zabezpieczenie.
Kod, który implementuje opublikowany port albo buduje jeden z jego rekordów, nie skompiluje się, dopóki nie ma nowego elementu — własna implementacja w module nakładkowym albo dubler testowy o typie portu:
| Gdzie | Nowy wymagany element |
|---|---|
AdminPermissionChecker (@endora-commerce/platform) | isActiveAdministrator(adminUserId) |
SalesChannelMembershipPort (@endora-commerce/platform) | entityIdsInChannelSubquery, filterEntityIdsAvailableInChannel, clearChannelsForEntity |
MfaLoginPort | invalidatePending(subject) |
CustomFieldValuePort | projectForCustomer(entityType, bag) |
DeliveryMethodReadPort, PaymentMethodReadPort | isAvailableInChannel(id, salesChannelId) |
CustomFieldDefinitionRecord, CreateCustomFieldDefinitionRequest | audience |
Order (typ wywnioskowany) | customFieldValues ({}), placedOnBehalf |
Wiersze bez podanego pakietu dotyczą @endora-commerce/contracts. Moduł, który zwraca wartości pól
niestandardowych komuś innemu niż administrator, przepuszcza zapisany zbiór wartości przez
CustomFieldValuePort.projectForCustomer w swoim serializatorze.
Usunięte. AdminAuthService.changePassword w @endora-commerce/mod-admin-users, razem z
czwartym argumentem konstruktora tej klasy — jedyną implementacją jest
AdminUserService.updateSelf, a AdminUserService.update nie przyjmuje już password.
ChallengeStore.recordFailedAttempt w @endora-commerce/mod-mfa, zastąpione przez takeAttempt
i returnAttempt. Oraz bindToDefaultChannel:
Jeśli Twój własny moduł tworzy metodę dostawy lub płatności w swoim installHook, funkcja
bindToDefaultChannel zniknęła z interfejsu tworzenia metod: usuń jej wywołanie po
ensureMethodForAdapter. Nic jej nie zastępuje — utworzona metoda jest dostępna w każdym kanale.
Źródła modułu nie skompilują się, dopóki tego nie zrobisz. Wcześniej opublikowana wersja modułu
nie jest kompilowana ponownie, więc zawiedzie później — błędem TypeError w installHook, gdy po
raz pierwszy będzie tworzyć swoją metodę — i trzeba ją wydać ponownie dla tego wydania. Instancji,
która ma już wiersz tej metody, to nie dotyczy, bo hook wykonuje to wywołanie tylko dla wiersza,
który właśnie utworzył. Nie ma mechanizmu tworzenia metody ograniczonej do wybranych kanałów;
ogranicz ją w panelu administracyjnym.
Wyniki wysyłki poczty zyskały logged. EmailMailerSendOutcome, TransactionalSendOutcome,
EmailDeliveryStatus i InvoiceEmailNotSentReason mają nowy element, którym konsolowy mailer
odpowiada zamiast sent. Kod z wyczerpującym switch po jednym z nich przestaje się kompilować,
dopóki nie obsłuży logged; kod, który odczytuje outcome.reason po
outcome.status !== 'sent', musi najpierw zawęzić typ do 'suppressed'; kod, który porównuje z
=== 'sent', kompiluje się dalej i traktuje teraz wiadomość zapisaną w logu jako niewysłaną.
logged nie jest błędem i nie ma czego ponawiać.
Kompozycja danych demonstracyjnych i kod resetu działają wewnątrz transakcji resetu.
- Obiekt zwracany przez
createDemoCompositionmusi deklarowaćwithdrawsInsideTransaction: true. Reset na kompozycji, która tego nie robi, jest odrzucany, zanim się zacznie. - Kod
demo.resetmodułu iwithdrawkompozycji muszą zapisywać przezEntityManager, który otrzymują (em.nativeDelete,em.execute), a nie przezem.getConnection().execute(…): samo połączenie jest poza transakcją, a w czasie resetu drugie połączenie jest odrzucane komunikatem a reset body wrote outside the reset transaction. @endora-commerce/platform/demoeksportujeDemoResetRefusedError, którym kompozycja odmawia resetu z komunikatem, orazDEMO_FORCE_DELETE_FINANCIAL_RECORDS_FLAG.
Zachowania, na które może trafić Twój kod.
AdminPasswordVerificationPort.verifyPasswordmoże teraz odrzucić wywołanie odpowiedzią429albo503limitu logowania, zamiast zawsze zwracać wartość logiczną, i przyjmuje opcjonalny trzeci argument opisujący pochodzenie żądania. Moduł, który sam weryfikuje dane uwierzytelniające administratora, korzysta z tych samych liczników przezadminAuthenticationThrottlePort.AuthSessionPort.destroyAllForAdminidestroyAllForCustomerprzyjmują opcjonalne{ exceptSessionId }. Implementacja, która je ignoruje, nadal przechodzi sprawdzenie typów i kończy także sesję wywołującego.validateUseOnStorefrontadaptera płatności lub dostawy otrzymuje identyfikator kanału zamówienia wsalesChannelIdprzy zamówieniu ze storefrontu, podczas gdy dotąd otrzymywałonull.- Subskrybent
order.created.v1albopromotion.used.v1na szynie zdarzeń w procesie działa po zatwierdzeniu transakcji zamówienia.product.archived.v1jest teraz emitowane na każdej ścieżce, która zmienia status produktu nainactive, a zapis czeka na subskrybentów. - Moduł udostępnia własne zdarzenia webhookom, dopisując ich nazwy do
webhookEventRegistryw hooku startowym i deklarując krawędź jakocontributes-to. CustomFieldValuesPanelw@endora-commerce/admin-kitprzekazuje dosaveionChangenull, a nieundefined, dla wyczyszczonej liczby, daty albo pola wyboru.- Nadpisanie motywu, które styluje dolną krawędź
.b2b-sidebar__brand, należy przenieść na.b2b-sidebar__brand-row, do którego należy teraz linia oddzielająca. npm_package_versionnie jest już zadeklarowaną zmienną środowiskową platformy.
Jeśli sam konstruujesz te klasy, a nie przez composeApp albo zestaw testowy:
CmsBlockService, CmsTemplateService i CmsHookService przyjmują CommandBus jako drugi
argument konstruktora; PasswordResetService przyjmuje port sesji jako drugi argument, a port
audytu jako trzeci; makeEnqueuer w @endora-commerce/mod-google-analytics przyjmuje drugi,
wymagany argument; a cykliczne konsumenty z mod-orders, mod-crm, mod-product-feeds i
mod-price-lists przyjmują pytanie, czy przebieg ma coś do zrobienia — dziennik zmian każdego
pakietu wymienia te elementy.
W istniejącym storefroncie
W istniejącym storefroncie, który zachowuje źródła, z jakimi go utworzono, ani wywołania
dotyczące zamówień, ani oba katalogi metod nie informują backendu, w którym kanale jest kupujący:
są wykonywane bez kontekstu żądania, więc nagłówek X-Sales-Channel nie jest wysyłany i backend
rozpoznaje dla nich kanał domyślny. W instancji z jednym kanałem sprzedaży to poprawna odpowiedź i
niczego nie trzeba zmieniać. W instancji z więcej niż jednym wprowadź wszystkie poniższe zmiany
razem — przy tylko części z nich składanie zamówienia pokazywałoby metody jednego kanału, a
zamówienie trafiałoby do innego i byłoby odrzucane:
- W
lib/api/orders.tsdodajimport type { RequestContext } from './client';, dodaj ostatni parametrctx: RequestContextdo funkcjiplaceOrder,previewOrderTotalicloneOrderToQuoteoraz dodajctx,do obiektu opcji, który każda z nich podaje doapiMutate. - W
lib/api/quick-order.ts, który już importujeRequestContext, zrób to samo dlaplaceOneClickOrder(apiMutate) igetOneClickEligibility(apiGetAuthed). - W
lib/api/methods.ts:- zmień pierwszy import na
import { apiGet, type RequestContext } from './client';; - dodaj parametr do obu eksportowanych funkcji i przekaż go dalej —
listDeliveryMethods(ctx: RequestContext)zwracającewithModuleAbsence(() => fetchDeliveryMethods(ctx), [])orazlistPaymentMethods(ctx: RequestContext)zwracającewithModuleAbsence(() => fetchPaymentMethods(ctx), []); - dodaj ten sam parametr do obu prywatnych funkcji,
fetchDeliveryMethods(ctx: RequestContext)ifetchPaymentMethods(ctx: RequestContext), i w każdej przekażctxjako drugi argument jej wywołaniaapiGet.
- zmień pierwszy import na
- W plikach
test/checkout/delivery-catalogue-absence.test.tsxitest/checkout/payment-catalogue-absence.test.tsx, które utworzony storefront zawiera i które obejmuje jegotsconfig.json, obie funkcje są wywoływane bez argumentu — po cztery razy w każdym pliku. Zmień każdelistDeliveryMethods()nalistDeliveryMethods({})i każdelistPaymentMethods()nalistPaymentMethods({}). Bez tego sprawdzenie typów storefrontu kończy się błędemExpected 1 arguments, but got 0w tych dwóch plikach. Pomiń ten krok, jeśli je usunąłeś. - Przekaż kontekst w miejscach wywołań.
getServerContextjest już importowany we wszystkich czterech plikach:app/(commerce)/checkout/page.tsx: komponent strony zawieraconst { locale } = await getServerContext();— zmień to naconst { locale, ctx } = await getServerContext();i przekażctxdo znajdujących się niżej wywołańlistDeliveryMethodsilistPaymentMethods. WsubmitAction, osobnej funkcji, dodajconst { ctx } = await getServerContext();przed wywołaniemplaceOrderi przekażctxjako ostatni argument.app/(account)/preferences/page.tsx: ta sama zmiana wierszaconst { locale } = await getServerContext();ictxprzekazane do obu wywołań list.app/(catalog)/p/[slug]/page.tsx: komponent strony ma jużctxw zasięgu, więc przekaż go dogetOneClickEligibilityi niczego nie deklaruj; woneClickAction, osobnej funkcji, dodajconst { ctx } = await getServerContext();przed wywołaniemplaceOneClickOrderi przekażctx.app/(account)/orders/[id]/page.tsx, wreorderToQuoteAction: dodajconst { ctx } = await getServerContext();przed wywołaniemcloneOrderToQuotei przekażctx.
getServerContext() bierze kanał z nagłówka x-sales-channel żądania, które otrzymuje sam
storefront — tego samego, który Twoje reverse proxy albo middleware już ustawia dla każdego hosta,
aby strony renderowały się we właściwym kanale.
Trzy kolejne zmiany, których storefront utworzony we wcześniejszym wydaniu nie otrzymuje:
-
Usuwanie subskrypcji push. W
lib/api/pwa.tsfunkcjaunsubscribeFromPush()wysyła sam endpoint, na co backend reaguje teraz tylko dla zalogowanego klienta, do którego subskrypcja należy. Wysyłaj razem z nim klucze — przedfetchodczytajconst json = subscription.toJSON();i ustaw treść żądania nabody: JSON.stringify({endpoint: subscription.endpoint,keys: { p256dh: json.keys?.['p256dh'] ?? '', auth: json.keys?.['auth'] ?? '' },}),Bez tego wyłączenie powiadomień w przeglądarce bez zalogowanego klienta zostawia subskrypcję zarejestrowaną w backendzie, dopóki usługa push nie zgłosi, że już jej nie ma.
-
Bloki katalogu w HTML renderowanym na serwerze.
ProductGrid,ProductSlider,ProductCard,CategoryListiCategoryGridna stronie CMS nadal pobierają dane w przeglądarce, jak dotąd, dopóki storefront nie rozwiąże ich danych na serwerze. Storefront zapisany przez CLI wydania0.105.0to robi: weź z niegocomponents/CatalogBlockData.tsxilib/page-builder/catalog-block-data.ts, weź jegocomponents/CmsPageRenderer.tsxicomponents/Hook.tsxoraz przekażctxdoCmsPageRendererwapp/page.tsxiapp/(content)/[...slug]/page.tsx— ta właściwość jest tam wymagana. Utwórz storefront, z którego skopiujesz pliki, tak jak w sekcji Bloki modułów w istniejącym storefroncie. -
Twoje własne typy.
lib/api/rfq.tsdeklarujecreatedByAdminUserId,assignedAdminUserIdiactorAdminUserId, których odpowiedzi dla klienta już nie zawierają; referencyjny storefront nie odczytywał żadnego z nich. Jeśli Twój kod je odczytuje — alboplacedOnBehalfByAdminUserIdzamówienia — odczyta terazundefined.
Storefront zapisany przez CLI wydania 0.105.0 ma też w stopce wzmiankę o platformie; istniejący
jej nie zyskuje.
Na ile zostało to sprawdzone: instancję utworzoną przez instalator wydania 0.104.0 z danymi
demonstracyjnymi — API, panel i storefront — zaktualizowano poleceniem pnpm run upgrade 0.105.0
z pnpm 9, z pakietami wydania udostępnionymi z lokalnego rejestru, i polecenie przeszło do końca.
Co w niej zaobserwowano:
- wykonały się dokładnie dwie migracje, odbiorców pól niestandardowych i znaczników przeczytania
wiadomości CRM; endpoint stanu i znaczek w panelu administracyjnym podają
0.105.0; - pięć błędnych haseł administratora otrzymuje
401, a szóste429 ADMIN_AUTHENTICATION_THROTTLEDzRetry-After: 60, co ustępuje po upływie opóźnienia albo po poleceniuunlock; żądanie bez sesji i z niepoprawną treścią otrzymuje401; - metody utworzone w panelu przed aktualizacją są dostępne tylko w kanale domyślnym, metody utworzone przez moduły — w każdym kanale, a metoda przypisana do innego kanału jest tam dostępna; metoda dostawy utworzona w panelu była oznaczona jako Nieoferowana przy składaniu zamówienia, a wybranie adaptera to naprawiło;
- zamówienie albo jego podgląd są odrzucane dla metody, której kanał nie oferuje, dla treści wskazującej inny kanał i dla metody spoza listy dozwolonych metod organizacji, a po każdej odmowie koszyk i liczba zamówień były bez zmian;
- ze storefrontem w postaci zapisanej przez
0.104.0składanie zamówienia w kanale innym niż domyślny pokazuje metody kanału domyślnego, a zamówienie jest zapisywane w kanale domyślnym; z powyższymi zmianami — naniesionymi dokładnie tak, jak je opisano, na siedem plików źródłowych i dwa pliki testów, przy czym każdy cytowany fragment został znaleziony — pokazuje metody tego kanału, zamówienie jest zapisywane w tym kanale, atsc --noEmit, testy storefrontu inext buildkończą się powodzeniem; - każda istniejąca definicja pola niestandardowego zachowuje
customer, a jej wartość nadal jest w zamówieniu kupującego; definicja utworzona później mainternali jej wartości tam nie ma; - szansa sprzedaży CRM sprzed aktualizacji nie pokazuje nikomu licznika nieprzeczytanych, a wiadomość napisana później jest nieprzeczytana tylko dla drugiego administratora;
- subskrypcja webhooka sprzed aktualizacji się wczytuje, a nieznany typ zdarzenia to
422.
Nie sprawdzono: instancji starszej niż 0.104.0, instancji z modułami nakładkowymi ani modułu,
który nadal wywołuje bindToDefaultChannel; zmiennych proxy, limitu dla całego konta i
ADMIN_AUTH_ACCOUNT_WIDE_LIMIT, limitu kodów drugiego składnika ani sesji kończonych przez zmianę
danych uwierzytelniających; zadania wygaszającego zapytania ofertowe; zakupu jednym kliknięciem,
strony preferencji ani ponownego zamówienia jako zapytania ofertowego w działaniu; formularza
tworzenia zamówienia w panelu ani przyjmowania zamówień z kluczem API; demo reset; dostarczenia
webhooka; zmian w storefroncie dotyczących subskrypcji push i bloków katalogu, wiersza w
compose.prod.yml ani wierszy .gitignore dla backend/var/; ani odtworzenia kopii zapasowej.
To, co ta sekcja o nich mówi, pochodzi z dzienników zmian wydania, sprawdzonych ze źródłami tego
wydania.
Po aktualizacji do wydania 0.106.0
pnpm run upgrade 0.106.0 przenosi 73 pakiety wydania z 0.105.0 i wykonuje jego jedyną migrację,
która dodaje kolumnę do tabeli settings i nie przepisuje żadnego wiersza; instancja uruchamia się
potem bez edycji żadnego ze swoich plików, a wydanie nie dodaje żadnej zmiennej środowiskowej ani
żadnego uprawnienia. To, co dodaje, znajduje się na karcie SEO w Ustawieniach: storefront
kanału sprzedaży można wyłączyć z wyszukiwarek, dopisać mu własne reguły do robots.txt i nadać
mu plik llms.txt — i żadna z tych trzech rzeczy nie dociera do storefrontu utworzonego we
wcześniejszym wydaniu, dopóki jego pliki nie zostaną zaktualizowane. Ta sekcja mówi, co zrobić i w
jakiej kolejności, a potem — co działa inaczej, osobno dla każdego czytelnika:
operatorów, klientów API i integratorów,
autorów modułów i modułów nakładkowych oraz właściciela
istniejącego storefrontu.
Co zrobić i w jakiej kolejności
Przed aktualizacją: nic poza tym, co opisuje Zanim zaczniesz. Migracja
Migration20261011T135250CoreSettingsMultiline dodaje kolumnę settings.multiline boolean not null default false, nie wymaga żadnego uzupełnienia danych i nie zmienia wartości ani znaczenia
żadnego ustawienia. Cofaj ją wyłącznie razem z pakietami: kod tego wydania oczekuje tej kolumny,
więc cofnięcie samej migracji zostawia instancję, która nie uruchomi się, podczas gdy cofnięcie
jej w ramach obniżenia wersji @endora-commerce/platform i @endora-commerce/mod-settings traci
tylko tę flagę.
Po aktualizacji, zanim instancja znów przyjmie ruch:
- Zdecyduj, czy storefront przejmie pliki z tego wydania. Trzy nowe ustawienia na karcie SEO —
Domyślne robots, Własne instrukcje robots.txt i Dodatkowa treść llms.txt — pojawiają się w
każdej zaktualizowanej instancji, a storefront utworzony we wcześniejszym wydaniu nie reaguje na
żadne z nich: kanał ustawiony na
NOINDEXnadal jest indeksowany,robots.txtpozostaje plikiem, z którym storefront został zbudowany, i nic nie serwujellms.txt. Wiersz Domyślne robots i oba podglądy informują o tym, dopóki tak jest. Zmiany opisuje część W istniejącym storefroncie; dopóki nie zostaną wprowadzone, nie polegaj na tych ustawieniach. - W storefroncie, który obsługuje więcej niż jeden kanał sprzedaży za reverse proxy albo siecią
CDN, spraw, by proxy samo ustawiało nagłówek
X-Sales-Channel, osobno dla każdego hosta, i nie przekazywało dalej wartości przysłanej przez odwiedzającego. Po zaktualizowaniu storefronturobots.txtillms.txtróżnią się między kanałami, a kanał wskazuje właśnie ten nagłówek. W nginx to jeden wiersz w blokulocationstorefrontu —proxy_set_header X-Sales-Channel "<kod kanału tego hosta>";we wdrożeniu wielokanałowym alboproxy_set_header X-Sales-Channel "";, aby usunąć nagłówek we wdrożeniu jednokanałowym, którego poza tym zmiana nie dotyczy. Pliknginx.example.conf, z którym utworzono instancję, nie zawiera tego wiersza;deploy/nginx.example.confw repozytorium platformy pokazuje oba warianty, zakomentowane. Współdzielona pamięć podręczna, która ignorujeVarydla tego nagłówka, musi włączyć go do klucza albo nie buforować/robots.txt. - Ustaw Domyślne robots dla każdego kanału sprzedaży, którego storefront ma pozostać poza
wyszukiwarkami —
NOINDEX, FOLLOWalboNOINDEX, NOFOLLOW, w Ustawienia → SEO — i nie dopisuj do tegoDisallow: /we własnych instrukcjachrobots.txt: robot, któremu nie wolno pobrać strony, nigdy nie odczyta jejnoindex. Przy zaktualizowanym storefronciecurl -sI https://<twój sklep>/ | grep -i x-robots-tagpokazuje nagłówek w ciągu minuty lub dwóch. - Sprawdź, czy
robots.txtstorefrontu nadal serwuje to, co powinien. Po wprowadzeniu zmian z części W istniejącym storefroncie lista tras w wyniku budowania musi pokazywaćƒ /robots.txt, a nie○ /robots.txt, acurl -sI https://<twój sklep>/robots.txt | grep -i x-endora-crawler-filesmusi wypisać nagłówek. Jeśli lista tras nadal pokazuje○, plikapp/robots.tsnie został usunięty i stary plik z czasu budowania nadal wygrywa — po cichu. Jeśli edytowano listędisallowwapp/robots.ts, te same wiersze muszą teraz być wROBOTS_SYSTEM_RULESwlib/seo/robots-system-rules.ts.
W dowolnym momencie:
- Jeśli utrzymujesz ustawienie
cms.reserved_slug_segments, dodaj do niegorobots.txtillms.txt, tak jak zmiany w storefroncie dodają je doRESERVED_TOP_LEVEL_SEGMENTSwapp/reserved-segments.ts. - Zbuduj własne moduły ponownie z tym wydaniem tylko wtedy, gdy któryś z Twoich testów
konstruuje
SettingWriteValidationInputalbo renderuje samodzielnieSettingRowEditorlubSettingsPage— część Dla autorów modułów i modułów nakładkowych mówi, czego każdy z nich potrzebuje. Moduł, który tylko dostarczaSettingWriteValidatoralbo tylko deklaruje ustawienia, nie wymaga zmian.
Dla operatorów
Storefront kanału sprzedaży można wyłączyć z wyszukiwarek jednym ustawieniem: Ustawienia → SEO →
Domyślne robots. Do wyboru są INDEX, FOLLOW (wartość domyślna), NOINDEX, FOLLOW,
INDEX, NOFOLLOW i NOINDEX, NOFOLLOW, dla wszystkich kanałów sprzedaży albo osobno dla kanału.
Dla kanału, którego wartość coś ogranicza, zaktualizowany storefront dodaje dyrektywę do tego, co
serwuje w tym kanale: nagłówek X-Robots-Tag na stronach (także na stronach „nie znaleziono” i na
stronie offline), na /sitemap.xml, na /_next/image i na plikach z katalogu public/, oraz
jeden dodatkowy element <meta name="robots"> w <head> stron renderowanych dla żądania. Strony
„nie znaleziono” i statycznie zbudowana strona /offline niosą nagłówek bez dodatkowego elementu —
obie same deklarują noindex, nofollow — a przekierowania zadeklarowane w next.config.js i pliki
w /_next/static nie niosą ani jednego, ani drugiego. Ustawienie tylko dodaje ograniczenia: strona,
która sama deklaruje noindex, zachowuje własny tag, a robots.txt się nie zmienia. Przy
wartości domyślnej nic nie jest dodawane. Zapisana zmiana obowiązuje w storefroncie w ciągu minuty
lub dwóch. Dwa ograniczenia: pliki serwowane przez backend (zdjęcia produktów i inne przesłane
media) nie niosą X-Robots-Tag, a storefront uruchomiony, gdy jego backend jest nieosiągalny,
serwuje strony bez domyślnej dyrektywy aż do pierwszego udanego odczytu. Ustawienie jest też
osiągalne z palety poleceń jako Roboty wyszukiwarek dla posiadacza settings:read. Zobacz
Roboty wyszukiwarek.
Do robots.txt kanału sprzedaży można dopisać reguły: Ustawienia → SEO → Własne instrukcje
robots.txt. Tekst jest dopisywany po regułach, które storefront generuje sam — za nimi,
oddzielony jednym pustym wierszem — dla wszystkich kanałów sprzedaży albo osobno dla kanału.
Generowane reguły (zamknięte rodziny adresów i wiersz Sitemap:) się nie zmieniają i nie można ich
edytować ani usunąć z ekranu Ustawień. Bez tekstu robots.txt jest bajt w bajt tym, czym był;
zresetowanie ustawienia usuwa tekst; zapisana zmiana jest serwowana w ciągu około dwóch minut.
- Tekst jest serwowany tak, jak go napisano: każdy znak jest zachowany, w tej samej kolejności, i
nic w nim nie jest walidowane. Dwie rzeczy dotyczące końców wierszy są ujednolicane — każdy
koniec wiersza jest serwowany jako znak nowej linii (
LF), niezależnie od tego, czy zapisano go jakoLF,CRLFczyCR, a plik kończy się dokładnie jednym — a jeden znacznik kolejności bajtów (U+FEFF) na samym początku tekstu nie jest serwowany, bo robot nie rozpoznaje wiersza, do którego jest doklejony. - Generowanych wierszy nie da się zmienić, ale ich skutek dla jednego robota — tak: grupa
napisana dla robota wskazanego z nazwy zastępuje dla niego grupę
*. Powtórz w takiej grupie generowane reguły, jeśli mają go nadal dotyczyć. Indeksowanie od tego nie zależy — strony, które te reguły zamykają, same deklarująnoindex. - Tekst może mieć najwyżej 64 KiB (65 536 bajtów UTF-8). Większy jest odrzucany przy zapisie
odpowiedzią
400 SEO_CRAWLER_TEXT_TOO_LARGEz komunikatem podającym limit, a zapisany wcześniej tekst zostaje zachowany. robots.txtnie usuwa stron z wyników wyszukiwania. Decyduje o tym, co robot może pobrać. Aby storefront pozostał poza wyszukiwarkami, użyj Domyślne robots =NOINDEXi nie dopisuj do tegoDisallow: /. Opis ustawienia mówi o tym, po angielsku i po polsku.
Zobacz Własne instrukcje robots.txt.
Pod polem znajduje się podgląd pliku, który serwuje storefront wybranego kanału, jako jeden
blok będący tym plikiem: wiersze generowane przez storefront, oznaczone jako tylko do odczytu,
potem pusty wiersz, potem Twoje wiersze, oznaczone paskiem i etykietą. Gdy nic nie jest
niezapisane, nagłówek brzmi robots.txt serwowany obecnie; w miarę pisania blok podąża za polem,
a nagłówek zmienia się na robots.txt po zapisaniu — plik, który storefront będzie serwował po
zapisaniu; do tego czasu serwuje poprzednią wersję. W widoku Wszystkie kanały wybierasz kanał
do podglądu, a podgląd wymienia kanały, które mają własną wartość i których zmiana w związku z tym
nie dotyczy. W kanale, którego Domyślne robots to NOINDEX, mówi, że robots.txt celowo
pozostaje otwarty, i ostrzega, gdy Twój tekst zabrania wszystkim robotom pobierania czegokolwiek.
Administrator, który może odczytywać ustawienia, ale nie może ich zmieniać, też go widzi, a temu,
kto nie może wyświetlać listy kanałów sprzedaży, pokazywany jest systemowy kanał domyślny.
Podgląd niczego nie zmienia w tym, co serwuje storefront.
Generowane reguły nie są kopiowane do backendu ani do panelu administracyjnego: za każdym razem
backend pyta o nie storefront kanału. Dlatego storefront musi być osiągalny z backendu pod
adresem skonfigurowanym dla kanału — ustawienie Storefront URL kanału, a w jego braku
STOREFRONT_BASE_URL. Gdy nie jest, podgląd o tym informuje i pokazuje sam Twój tekst; zapis
nigdy nie jest blokowany. Trzy komunikaty i co z każdym zrobić:
- Nie udało się połączyć ze sklepem — brak odpowiedzi w ciągu trzech sekund, odrzucone
połączenie, błąd, przekierowanie albo odpowiedź, która nie jest plikiem tekstowym. Sprawdź, czy
storefront działa i czy skonfigurowany adres to ten, pod którym sam odpowiada. Wiersz dziennika
serwera
seo: the crawler-file preview could not show the storefront’s partpodaje przyczynę. - Ten kanał sprzedaży nie ma skonfigurowanego adresu sklepu — ustaw Storefront URL dla
kanału albo
STOREFRONT_BASE_URL. - Ten sklep nie potwierdził, że obsługuje własne instrukcje — odpowiedział bez nagłówka
X-Endora-Crawler-Files. Zwykle storefront został utworzony w wydaniu sprzed tych ustawień: serwuje tylko swoje generowane reguły i ignoruje zarówno własne instrukcje, jak i Domyślne robots. Tak samo wygląda proxy, które usuwa ten nagłówek, albo storefront zaktualizowany tylko częściowo. Odpowiednia informacja pojawia się w wierszu Domyślne robots, żeby nie uznać sklepu za wyłączony z wyszukiwarek, gdy jego storefront może ignorować to ustawienie. Wprowadź zmiany z części W istniejącym storefroncie i sprawdź, czy nagłówek dociera.
Backend wysyła jedno żądanie na podgląd: GET <origin>/robots.txt?part=system, wyłącznie do
originu skonfigurowanego adresu, z kodem kanału w nagłówku X-Sales-Channel. Nie podąża za
przekierowaniem, czeka najwyżej trzy sekundy, czyta najwyżej 512 KiB, przyjmuje wyłącznie
text/plain, nie wysyła ciasteczek ani danych uwierzytelniających i niczego nie zapisuje. Ścieżka
ani zapytanie w skonfigurowanym adresie nie są używane. Adresy prywatne i pętli zwrotnej nie
są odrzucane, bo własny storefront instancji jest rutynowo osiągany pod takim adresem; o tym, kto
może wskazać adres kanału, decyduje jak dotąd posiadanie settings:write.
Storefront serwuje llms.txt osobno dla każdego kanału sprzedaży, a Ty możesz go uzupełnić:
Ustawienia → SEO → Dodatkowa treść llms.txt. /llms.txt to plik tekstowy, który mówi
asystentom AI, czym jest sklep i gdzie są jego publiczne strony. Zaktualizowany storefront serwuje
po jednym dla każdego kanału sprzedaży: część generowaną — nazwę sklepu, odnośniki do strony
głównej, katalogu, wyszukiwania, bloga i strony kontaktowej oraz do mapy witryny — a po niej tekst
zapisany w tym ustawieniu. Ustawienie jest domyślnie puste, przyjmuje wartość dla wszystkich
kanałów sprzedaży i wartość osobno dla kanału jak każde inne i mieści najwyżej 64 KiB — limit
instrukcji robots.txt. Zapisana zmiana jest serwowana w ciągu około dwóch minut. Adresy w
generowanej części używają jedynego publicznego originu storefrontu (NEXT_PUBLIC_SITE_URL,
ustalonego przy budowaniu storefrontu), tak jak mapa witryny: storefront obsługujący kilka kanałów
sprzedaży podaje ten origin w pliku każdego kanału, z nazwą sklepu właściwą dla tego kanału. Dwie
rzeczy, które warto wiedzieć, zanim na nim polegasz:
- Kanał sprzedaży, którego Domyślne robots to
NOINDEX, nie mallms.txt. Storefront odpowiada tam na/llms.txt„nie znaleziono”, niezależnie od zawartości ustawienia: sklep, który prosi wyszukiwarki, by trzymały się z daleka, nie zaprasza asystentów AI. Tekst jest zachowany i serwowany ponownie, gdy kanał zezwoli na indeksowanie.robots.txtto nie dotyczy — w takim kanale pozostaje otwarty, jak dotąd. - Wyłączenie modułu
seowycofuje ten plik, razem z polityką Domyślne robots i własnymi instrukcjamirobots.txt(poniżej).
Pod polem znajduje się podgląd, taki sam jak przy Własnych instrukcjach robots.txt: jeden blok
będący plikiem, z Twoimi wierszami oznaczonymi w środku, pod nagłówkiem llms.txt serwowany
obecnie albo llms.txt po zapisaniu. W kanale NOINDEX zamiast bloku mówi llms.txt nie jest
serwowany, z podaniem przyczyny. Komunikaty dla storefrontu, z którym nie udało się połączyć,
który nie ma skonfigurowanego adresu albo nie potwierdził, że serwuje plik, są tymi, które pokazuje
podgląd robots.txt, a żądanie, które backend wysyła w tym celu, jest ograniczone tak samo — jedno
GET <origin>/llms.txt?part=base, bez podążania za przekierowaniem, trzy sekundy, 512 KiB,
wyłącznie text/plain, nic z żądania administratora w środku. Jak dotąd adresy prywatne i pętli
zwrotnej nie są odrzucane, a ten podgląd mówi posiadaczowi settings:read nieco więcej o
skonfigurowanym adresie niż podgląd robots.txt: 404 bez nagłówka storefrontu jest zgłaszane
jako „nie potwierdził, że serwuje plik llms.txt”, a nie jako „nie udało się połączyć”, więc adres,
pod którym odpowiada jakiś serwer HTTP, da się odróżnić od takiego, pod którym nie odpowiada nic.
Zobacz Dodatkowa treść llms.txt.
Wyłączenie modułu seo wycofuje wszystkie trzy. Storefront nie wysyła domyślnej dyrektywy
robots, więc kanał ustawiony na NOINDEX znów jest indeksowany, dopóki moduł nie zostanie
ponownie włączony; robots.txt jest serwowany z samymi generowanymi regułami; a llms.txt nie
jest serwowany. Zapisane wartości są zachowane i obowiązują ponownie po włączeniu modułu. Opis
modułu i jego ustawienie aktywacji na ekranie /platform/modules mówią o tym. Zobacz
Gdy moduł jest wyłączony.
robots.txt nie zależy już od tego, czy Domyślne robots da się odczytać. Wartość Domyślne
robots spoza czterech dostępnych — co wymaga wiersza zapisanego z pominięciem API ustawień —
sprawiała, że GET /api/v1/storefront/seo/crawler-files odpowiadało 500, więc świeżo
uruchomiony storefront odpowiadał na robots.txt tego kanału kodem 503, choć ten plik nigdy od
tego ustawienia nie zależał. Endpoint odpowiada teraz i wskazuje, czego nie udało się odczytać.
Taki kanał zachowuje swój robots.txt; jego llms.txt, który od tego ustawienia zależy, odpowiada
503, dopóki wartość nie zostanie naprawiona przez ponowne zapisanie Domyślne robots. Korzysta
na tym także niezaktualizowany storefront: jego robots.txt również jest serwowany.
Ślad audytowy zmiany wartości ustawienia zapisuje wartość sprzed zmiany. Dotąd
setting.global_value_set i setting.value_set niosły tylko stateAfter, a oba resety
(setting.global_value_reset, setting.value_reset) odnotowywały, że wartość została usunięta,
nie odnotowując której. Każde z tych czterech zdarzeń niesie teraz także stateBefore:
{ "settingCode": "your_module.base_url", "value": "https://old.example", "valueType": "string", "stored": true }
a w dwóch akcjach dotyczących kanału dodatkowo salesChannelId. Przeglądarka dziennika audytu już
renderuje stateBefore obok stateAfter, więc poprzednia wartość pojawia się tam bez żadnej
dalszej zmiany. value to wartość zapisana na poziomie, na którym następuje zapis — wartość
dla wszystkich kanałów przy zapisie lub resecie dla wszystkich kanałów, własna wartość danego
kanału przy zapisie lub resecie dla kanału — a nie wartość, do której kanał się rozstrzygnął:
pierwszy zapis odnotowuje "value": null, "stored": false, nie wartość domyślną z manifestu ani
odziedziczoną wartość dla wszystkich kanałów. stored odróżnia „nic nie było zapisane” od
zapisanego null, który ustawienie typu json może zgodnie z prawem przechowywać. Poprzednia
wartość ustawienia typu secret jest odnotowywana jako "[redacted]", dokładnie tak, jak już
była odnotowywana jego nowa wartość — ani tekst jawny, ani zapisany szyfrogram nie trafia do śladu
audytowego — a credential_ref odnotowuje po obu stronach kod konfiguracji, jak dotąd. Sam zapis
się nie zmienia, odrzucony zapis nadal niczego nie odnotowuje, a wpisy zapisane przed tym wydaniem
zachowują pusty stan sprzed zmiany.
Ekran Ustawień może pokazywać nazwę i opis ustawienia w języku administratora. Dotąd pokazywał
name i description zadeklarowane w manifeście ustawień, po angielsku, niezależnie od języka, w
którym pracuje administrator. Teraz najpierw szuka ich we własnym pakiecie tłumaczeń modułu
będącego właścicielem ustawienia, a gdy brakuje klucza, wraca do zadeklarowanego tekstu; filtr na
ekranie dopasowuje przetłumaczoną nazwę i opis tak samo jak zadeklarowane, a opcje ustawienia z
enumOptions są wartościami zapisywanymi i pozostają nieprzetłumaczone. Dla operatora nic się
nie zmienia, dopóki moduł nie dostarczy tych kluczy. Wydanie dodaje tylko sam mechanizm
wyszukiwania i żadne istniejące wcześniej ustawienie nie jest przez niego tłumaczone; jedynym
modułem, który w tym wydaniu dostarcza klucze, jest seo — dla swojej grupy i czterech ustawień
(SEO włączone, Domyślne robots, Własne instrukcje robots.txt, Dodatkowa treść llms.txt), po
angielsku i po polsku. Własny moduł dodaje je tak, jak opisuje część
Dla autorów modułów i modułów nakładkowych.
Ustawienie może być wielowierszowe. Ustawienie typu string, które jego moduł tak zadeklaruje,
jest edytowane na ekranie Ustawień w polu wielowierszowym, które dokładnie zachowuje końce
wierszy, puste wiersze, wcięcia i końcowy znak nowej linii; pole jest nazwane po ustawieniu na
potrzeby technologii wspomagających, a wiersz pod nim, który podgląda wartość domyślną i bieżącą,
pokazuje jeden skrócony wiersz zamiast całego tekstu. W tym wydaniu takimi ustawieniami są oba
teksty SEO. Każde istniejące wcześniej ustawienie pozostaje jednowierszowe, więc ekran Ustawień
wygląda i zachowuje się dokładnie tak jak dotąd, dopóki zainstalowany moduł nie zadeklaruje tej
flagi.
Dla klientów API i integratorów
- Dwa publiczne endpointy storefrontu rozstrzygane dla kanału.
GET /api/v1/storefront/seo/robots-policyodpowiada{ "data": { "index", "follow" } }zCache-Control: no-store;503 MODULE_DISABLED, gdy modułseojest wyłączony; i500— nigdy odpowiedzią zezwalającą — gdy zapisana wartość nie jest jedną z czterech dostępnych.GET /api/v1/storefront/seo/crawler-filesodpowiada{ "data": { "index", "follow", "robotsTxtCustom", "llmsTxtAdditional", "shop": { "name" } } }zCache-Control: no-store,503 MODULE_DISABLED, gdy moduł jest wyłączony, i500— nigdy pustym tekstem — gdy zapisanych instrukcjirobots.txtnie da się zaserwować. Jegodatama opcjonalny elementunreadable: nazwy elementów, których backend nie zdołał odczytać z tego, co zapisano —'policy'(indexifollowsą wtedy obafalsei znaczą „nieznane”) oraz'llmsTxtAdditional'(ten element jest wtedy''i znaczy „nieznane”). Brakuje go, gdy wszystko udało się odczytać, a konsument, który odczytuje z tego endpointuindex,followlubllmsTxtAdditional, powinien traktować wymieniony element jako nieznany, a nie jako jego wartość. Oba schematy odrzucają elementy, których nie znają, więc późniejsze wydanie może dodać nowy bez odrzucenia odpowiedzi przez starszego konsumenta. Kanał sprzedaży to rozstrzygnięty kanał żądania, jak na każdej trasie storefrontu. - Endpoint podglądu dla administratorów.
GET /api/v1/admin/seo/crawler-files/preview?file=robots|llms&salesChannelCode=<code>, chroniony uprawnieniemsettings:read.salesChannelCodejest opcjonalny i oznacza systemowy kanał domyślny; odpowiedź wskazuje kanał, którego dotyczy, i jeden z pięciu stanów:ok,storefront-outdated(storefront odpowiedział bez nagłówkaX-Endora-Crawler-Files; brakbase),not-served(404z nagłówkiem — brak pliku dla tego kanału, czym jestllms.txtw kanaleNOINDEX),storefront-unreachableiaddress-unknown. - Zapis zbyt długiego tekstu dla robotów odpowiada
400 SEO_CRAWLER_TEXT_TOO_LARGEz komunikatem podającym limit 65 536 bajtów UTF-8; zapisany wcześniej tekst zostaje zachowany. SettingDtozyskujemultiline: boolean, któreGET /api/v1/admin/settingsiGET /api/v1/admin/settings/:codeteraz zawsze wysyłają. Odczytaj je, jeśli renderujesz własny edytor ustawienia.- Cztery akcje audytu zyskują
stateBefore—setting.global_value_set,setting.value_set,setting.global_value_resetisetting.value_reset— w kształcie podanym w części Dla operatorów.stateAfterzachowuje swój kształt. - Szczegóły automatyzacji newslettera —
GET /api/v1/admin/newsletter/automations/:idoraz odpowiedź każdego zapisu, który zwraca szczegóły — niosąsalesChannelId: nulldla automatyzacji bez przypisanego kanału sprzedaży, także po ponownym wczytaniu rekordu z bazy danych; wcześniej tego elementu w nich brakowało. Lista, która nigdy tego elementu nie niosła, się nie zmienia. - Nowe w
@endora-commerce/contracts, wszystko eksportowane z korzenia pakietu i z podścieżkiseo:storefrontRobotsPolicySchema,storefrontRobotsPolicyResponseSchemaoraz typyStorefrontRobotsPolicyiStorefrontRobotsPolicyResponse;storefrontCrawlerFilesSchema,storefrontCrawlerFilesResponseSchema, typyStorefrontCrawlerFilesiStorefrontCrawlerFilesResponse,CRAWLER_FILES_UNREADABLE_POLICYiCRAWLER_FILES_UNREADABLE_LLMS_TXT;composeCrawlerFile(base, extra)— opisane wyżej złączenie części generowanej z tekstem operatora, którym storefront serwuje plik, a podgląd go renderuje;CRAWLER_TEXT_MAX_BYTESiERROR_CODES.SEO_CRAWLER_TEXT_TOO_LARGE;adminCrawlerFilePreviewQuerySchema,adminCrawlerFilePreviewResponseSchema,adminCrawlerFilePreviewSchema,adminCrawlerFilePreviewStateSchema,adminCrawlerFileSchemai ich wywnioskowane typy; orazCRAWLER_FILES_CAPABILITY_HEADER(X-Endora-Crawler-Files) — nagłówek odpowiedzi, którym storefront informuje, że serwuje pliki dla robotów w bieżący sposób. SettingWriteValidationInputzyskujeproposedValue— wartość, którą proponuje zapis ustawienia, obok rzutowania na kanały, które już niósł. Zapis dla wszystkich kanałów nie dociera do żadnego kanału mającego własną wartość, więc w instancji, w której każdy kanał ją ma, proponowana wartość nie była w rzutowaniu żadnego kanału i walidator, którego reguła dotyczy samej wartości, nie mógł jej zobaczyć. Kod, który konstruuje to wejście, musi teraz podać ten element; zobacz Dla autorów modułów i modułów nakładkowych.
Dla autorów modułów i modułów nakładkowych
Ustawienie typu string można zadeklarować jako wielowierszowe. Dodaj multiline: true do
jego wpisu w manifeście ustawień modułu:
defineModuleSettingsManifest({
moduleCode: 'your_module',
settings: [
{
code: 'your_module.footer_note',
name: 'Footer note',
valueType: 'string',
defaultValue: '',
multiline: true,
},
],
});
Nic więcej się nie zmienia: wartość nadal jest typu string, jest odczytywana przez
SettingsService.get jak dotąd i rozstrzyga się dla kanału sprzedaży jak każde inne ustawienie.
To, co wpisano, jest tym, co zapisano. SettingManifestEntrySchema przyjmuje multiline tylko
przy valueType: 'string' i nie razem z enumOptions; każda inna kombinacja zawodzi przy
parsowaniu manifestu, z błędem wskazanym na multiline. Flaga dotyczy prezentacji, ManifestReconciler
utrzymuje ją w zgodzie z manifestem przy każdym starcie w obu kierunkach, więc jej późniejsze
dodanie lub usunięcie nie jest zmianą łamiącą zgodność i nigdy nie dotyka zapisanej wartości.
Moduł może dołożyć coś pod ustawieniem, które zadeklarował: strefa settings.setting.after.
AdminZoneNameSchema ma nowy element, montowany przez ekran Ustawień raz na wiersz ustawienia,
pod polem. Dołóż do niej, aby pokazać, co z ustawienia wynika — podgląd, konsekwencję, ostrzeżenie:
import { zoneComponent, type AdminContributions } from '@endora-commerce/admin-kit/contributions';
export const contributions: AdminContributions = {
zones: [
zoneComponent('settings.setting.after', () => import('./zones/FooterNotePreview.js'), {
match: { settingCode: 'your_module.footer_note' },
requiredPermission: 'settings:read',
}),
],
};
Komponent otrzymuje SettingsSettingAfterZoneProps: settingCode, ownerModule, channelCode
(edytowany kanał sprzedaży albo null dla wartości dla wszystkich kanałów), draftText (tekst w
polu w tej chwili, zapisany lub nie, dla ustawienia typu string; null dla każdego innego
typu wartości) oraz dirty. Zawęź przez match po settingCode albo ownerModule — fragment
niedopasowanego wkładu nigdy nie jest pobierany. Właściwości płyną w jedną stronę: wkład nie może
zmienić wartości ani wstrzymać zapisu wiersza i powinien powiedzieć słowami, gdy to, co pokazuje,
nie jest jeszcze zapisane. Strefa nie jest montowana dla ustawienia typu secret, więc wkład nigdy
nie otrzymuje roboczej wartości sekretu. Wkład znika razem ze swoim modułem, a zawodzący wkład nie
pociąga za sobą wiersza. Oba podglądy SEO są wkładami do tej strefy.
Jeśli sam renderujesz SettingRowEditor. Edytor wiersza w @endora-commerce/mod-settings
montuje teraz strefę, więc musi być renderowany pod AdminContributionsProvider i dostawcami
sesji, jak każdy ekran hosta montujący strefę. Panel administracyjny już to robi; test, który
renderuje wiersz albo SettingsPage samodzielnie, potrzebuje wokół nich tych dostawców.
Moduł może tłumaczyć nazwy i opisy własnych ustawień. Ekran Ustawień szuka ich w pakiecie tłumaczeń modułu będącego właścicielem pod trzema rodzinami kluczy i wraca do zadeklarowanego tekstu, gdy klucza brakuje:
| Klucz | Zastępuje |
|---|---|
settingLabels.<kod ustawienia>.name | name ustawienia |
settingLabels.<kod ustawienia>.description | description ustawienia |
settingGroups.<kod grupy>.name | name grupy |
Kod jest zapisywany w całości, z kropkami, a klucze są płaskie jak każdy inny klucz w pakiecie
tłumaczeń. Dla modułu your_module, który deklaruje ustawienie your_module.base_url w grupie
your_section, dodaj do i18n/pl.json (a angielskie brzmienie do i18n/en.json):
{
"settingGroups.your_section.name": "Twoja sekcja",
"settingLabels.your_module.base_url.name": "Bazowy adres URL",
"settingLabels.your_module.base_url.description": "Adres, pod którym usługa jest dostępna."
}
Rozstrzyganie odbywa się dla każdego klucza z osobna — język administratora, potem angielski,
potem tekst z manifestu — więc ustawienia można tłumaczyć pojedynczo. Odczytywany jest wyłącznie
pakiet tłumaczeń modułu, którego manifest ustawień zadeklarował dany wiersz (jego moduleCode,
który musi być równy id modułu); klucz dla ustawienia innego modułu jest ignorowany. Tekst jest
pokazywany tak, jak go napisano, bez podstawiania parametrów. name i description w manifeście
pozostają wymagane: to je zwraca API i to one są ostatnim wariantem zapasowym.
Kod, który konstruuje SettingWriteValidationInput, musi podać proposedValue — zwykle
test wywołujący walidator bezpośrednio. Moduł, który dostarcza SettingWriteValidator, nie
wymaga zmian, a taki, którego reguła dotyczy samej wartości, może ją teraz tam odczytać.
check:channel-resolution nie zgłasza już nagłówka, który jest wysyłany. Pierwszy sygnał tej
reguły oznacza każdy literał tekstowy x-sales-channel, bo moduł nie może odczytywać tego nagłówka
z żądania — robi to resolver. Zgłaszany był również moduł, który sam wysyła żądanie do innego
serwera i wskazuje w nim kanał sprzedaży. Klucz literału obiektu pod właściwością headers: —
fetch(url, { headers: { 'X-Sales-Channel': channel.code } }) — jest teraz wyłączony z reguły, tak
jak już wcześniej tablica allowedHeaders polityki CORS. Co nadal jest zgłaszane: dostęp
indeksowy do obiektu nagłówków, argument .get(...), nazwa trzymana w stałej, klucz obliczany,
deklaracja destrukturyzująca oraz przypisanie destrukturyzujące
(({ headers: { 'x-sales-channel': code } } = request), także jako zmienna pętli for…of lub
for…in), które ma wyłączony kształt, a jest odrzucane ze względu na swoje położenie.
@endora-commerce/mod-seo deklaruje lucide-react jako opcjonalną zależność peer, tak jak
pozostałe pakiety modułów z powierzchnią w panelu administracyjnym; aplikacja, która ma panel
administracyjny, już ją ma.
W istniejącym storefroncie
Istniejący storefront nie zmienia się przez aktualizację i dopóki nie zostanie zaktualizowany,
ignoruje wszystkie trzy ustawienia. endora new storefront kopiuje wzorzec raz, a potem projekt
należy do Ciebie. Przy zaktualizowanym backendzie i starszym storefroncie ekran Ustawień pokazuje
trzy ustawienia i nic na nie nie reaguje: kanał ustawiony na NOINDEX nadal jest indeksowany,
robots.txt pozostaje plikiem, z którym storefront został zbudowany, i nic nie serwuje
llms.txt. Aby przejąć te funkcje, przenieś poniższe zmiany ze wzorcowego storefrontu do projektu
storefrontu, zachowując własne edycje. Storefront do kopiowania utwórz tak, jak opisuje
Bloki modułów w istniejącym storefroncie. Trzy grupy budują jedna na
drugiej i trzeba je wprowadzać w tej kolejności; każdą można przejąć bez następnych.
1. Domyślne robots — cztery pliki, bez podnoszenia zależności. Nie importują niczego z pakietu, którego wcześniejszy storefront by nie miał, więc nic nie trzeba podnosić, aby się zbudowały i działały:
lib/seo/crawler-policy.ts— nowy: odczytuje politykę, buforuje ją na minutę dla kanału i zachowuje ostatnio odczytaną wartość, gdy backend jest nieosiągalny;lib/seo/robots-directive.ts— nowy: co polityka umieszcza w odpowiedzi;middleware.ts— zmieniony: odczyt polityki, nagłówek odpowiedzi, nagłówek żądania dla layoutu i rozszerzonyconfig.matcher. Middleware działa teraz dla każdej ścieżki poza/_next/static(dotąd pomijał pliki publiczne,/sitemap.xml,/robots.txt,/manifest.webmanifesti/_next/image); dla tych ścieżek tylko dodaje nagłówek. Zmierzone na wzorcowym storefroncie, kosztuje to plik publiczny około jednej milisekundy. Storefront, który już zna politykę kanału, nigdy nie czeka na odświeżenie dłużej niż ułamek sekundy; czeka się tylko na pierwszy odczyt po starcie, najwyżej dwie sekundy, raz;app/layout.tsx— zmieniony: renderuje dodatkowy<meta name="robots">w obu dokumentach, które zwraca. Nie przenoś dyrektywy do eksportumetadatalayoutu: własnerobotsstrony zastępuje tamrobotslayoutu, więc zostałaby pominięta na każdej stronie, która je deklaruje.
Potem zbuduj storefront ponownie. curl -sI https://<twój sklep>/ | grep -i x-robots-tag pokazuje
nagłówek po zapisaniu wartości ograniczającej. Testy wzorcowego storefrontu dla tych plików
(test/seo/crawler-policy.test.ts, robots-directive.test.ts, middleware-robots.test.ts,
middleware-effect.test.ts, layout-robots.test.ts, layout-outage-render.test.tsx i
test/conformance/robots-directive.spec.ts) można skopiować opcjonalnie; jeden z nich,
crawler-policy.test.ts, porównuje własne sprawdzenie odpowiedzi w storefroncie z opublikowanym
schematem i dlatego wymaga w projekcie storefrontu @endora-commerce/contracts w tym wydaniu lub
nowszym; nic innego w tej grupie tego nie wymaga.
2. Własne instrukcje robots.txt — robots.txt staje się trasą, a projekt potrzebuje
@endora-commerce/contracts w wersji 0.106.0 lub nowszej. lib/seo/robots-txt.ts importuje
composeCrawlerFile z @endora-commerce/contracts/seo, czego cztery powyższe pliki nie
potrzebowały. middleware.ts i app/layout.tsx nie zmieniają się w tej grupie:
- usuń
app/robots.ts. Musi zniknąć i nic Ci nie powie, jeśli nie zniknął: gdy istnieje razem zapp/robots.txt/route.ts, Next buduje się bez ostrzeżenia, dalej serwuje stary plik z czasu budowania i nigdy nie dociera do nowej trasy. Lista tras w wyniku budowania pokazuje, który wygrał —○ /robots.txtto stary plik,ƒ /robots.txtto nowa trasa; - dodaj
app/robots.txt/route.ts— trasę; - dodaj
lib/seo/robots-txt.ts— to, czym trasa odpowiada, i kiedy odpowiada503; - dodaj
lib/seo/robots-system-rules.ts— generowane reguły, przeniesione bez zmian zapp/robots.ts. Jeśli edytowano listędisalloww Twoimapp/robots.ts, przenieś te zmiany doROBOTS_SYSTEM_RULESw tym pliku; - zastąp
lib/seo/crawler-policy.tsnową wersją. Odczytuje teraz pliki dla robotów obok domyślnej polityki robots i porzuca odczyt z backendu, który zamrożony proces zostawił w zawieszeniu, zamiast czekać na niego w nieskończoność; - edytuj
app/reserved-segments.ts: dodaj'robots.txt'doRESERVED_TOP_LEVEL_SEGMENTSi dodaj ten sam wpis do ustawieniacms.reserved_slug_segmentswdrożenia, jeśli je utrzymujesz; - w
test/seo/route-declarations.test.ts, jeśli go skopiowano, zastąp import z'../../app/robots'importemROBOTS_SYSTEM_RULESirobotsSitemapUrlz'../../lib/seo/robots-system-rules', tak jak robi to wersja wzorcowa.
Potem zbuduj storefront ponownie;
curl -sI https://<twój sklep>/robots.txt | grep -i x-endora-crawler-files pokazuje nagłówek, gdy
serwuje już nowa trasa. Czym trasa odpowiada:
200z generowanymi regułami i tekstem kanału;Content-Type: text/plain; charset=utf-8,Cache-Control: public, max-age=60,Vary: X-Sales-Channel,X-Endora-Crawler-Files: 1. Dotąd plik był prerenderowany i wysyłany zCache-Control: public, max-age=0, must-revalidateiContent-Type: text/plain;- samymi generowanymi regułami, gdy moduł
seojest wyłączony, oraz wobec backendu, który nie ma tej funkcji; 503zRetry-After: 120, gdy tekstu kanału nie da się odczytać, a proces storefrontu nigdy go nie odczytał — zamiast plikiem, który po cichu pomija reguły operatora. Proces, który raz odczytał tekst, serwuje tę wersję tak długo, jak backend jest nieosiągalny. Storefront uruchomiony, gdy jego backend nie działa, odpowiada więc narobots.txtkodem503, dopóki backend nie wróci; stron to nie dotyczy;GET /robots.txt?part=systemodpowiada samymi generowanymi regułami, bez pytania backendu przez trasę, i jest wysyłane zCache-Control: no-store.
Origin w wierszu Sitemap: to nadal NEXT_PUBLIC_SITE_URL wkompilowany przy budowaniu. Testy
wzorcowego storefrontu dla tych plików (test/seo/robots-system-rules.test.ts z jego fixture
test/seo/__fixtures__/robots-before.txt, test/seo/robots-txt-route.test.ts oraz uzupełnienia
w test/seo/crawler-policy.test.ts) można skopiować opcjonalnie. Fixture to własne generowane
reguły wzorca z originem http://localhost:3000; projekt, który zmienił swoją listę, dostosowuje
fixture do niej.
3. llms.txt — zakłada, że grupy 1 i 2 są na miejscu: lib/seo/crawler-policy.ts,
lib/seo/robots-txt.ts, lib/seo/robots-system-rules.ts, lib/seo/robots-directive.ts i
app/robots.txt/route.ts istnieją, a app/robots.ts jest usunięty. middleware.ts i
app/layout.tsx nie zmieniają się także w tej grupie:
- dodaj
app/llms.txt/route.ts— trasę; - dodaj
lib/seo/llms-txt.ts— to, czym trasa odpowiada, i kiedy odpowiada404i503; - dodaj
lib/seo/llms-txt-base.ts— część generowaną. WymieniaSITEMAP_STATIC_ROUTESz Twojegoapp/sitemap.ts; jeśli zmieniono tę listę, nadaj każdej ze swoich tras tytuł wLLMS_TXT_PAGE_TITLESi wskaż wLLMS_TXT_CONDITIONAL_PAGESte z nich, które mogą odpowiedzieć „nie znaleziono”; - dodaj
lib/seo/llms-txt-pages.ts— czy strona warunkowa jest w tej chwili serwowana (dziś: blog), zapamiętywane dla kanału w pamięci podręcznej eksportowanej przezlib/seo/crawler-policy.ts; - dodaj
lib/seo/crawler-file-response.ts— nagłówki i tymczasową awarię wspólne dla obu plików; - zastąp
lib/seo/robots-txt.tsilib/seo/crawler-policy.tsnowymi wersjami. Pierwszy bierze teraz nagłówki zcrawler-file-response.ts; drugi odczytujeunreadablei eksportuje pamięć podręczną kanału, z której korzysta pytanie o stronę; - edytuj
lib/seo/robots-directive.ts:isDocumentPathmusi odpowiadaćfalsedla/llms.txt, tak jak wersja wzorcowa. Nie pomijaj tego kroku: bez niego storefront nadal serwuje plik, ale gdy backend jest nieosiągalny, odpowiada na/llms.txtstroną awarii zamiast ostatnią znaną wersją; - edytuj
app/reserved-segments.ts: dodaj'llms.txt'doRESERVED_TOP_LEVEL_SEGMENTSi dodaj ten sam wpis do ustawieniacms.reserved_slug_segmentswdrożenia, jeśli je utrzymujesz; - upewnij się, że nie ma pliku
public/llms.txt. Plik w tym miejscu jest serwowany zamiast trasy — dla każdego kanału, bez tekstu operatora i w kanale ustawionym naNOINDEX— a ani budowanie, ani serwer tego nie zgłaszają.curl -sI https://<twój sklep>/llms.txt | grep -i x-endora-crawler-filespokazuje nagłówek, gdy odpowiada trasa.
Potem zbuduj storefront ponownie — lista tras w wyniku budowania pokazuje ƒ /llms.txt. Testy
wzorcowego storefrontu dla tych plików (test/seo/llms-txt-base.test.ts,
test/seo/llms-txt-route.test.ts oraz uzupełnienia w test/seo/crawler-policy.test.ts,
test/seo/robots-txt-route.test.ts, test/seo/middleware-robots.test.ts i
test/seo/middleware-effect.test.ts) można skopiować opcjonalnie.
Podpis w stopce. Storefront utworzony przez CLI 0.106.0 pokazuje podpis „Built with ❤️ using
Endora Commerce” jako ostatni wiersz stopki — osobny wiersz pod wszystkim, co stopka renderuje
(kolumnami, wierszem praw autorskich i tym, co moduł dodaje przez hooki footer.bottom i
footer.copyright), wyśrodkowany, w jednym wierszu, dopóki szerokość na to pozwala, ze znakiem
Endora Commerce obok nazwy produktu — podczas gdy dotąd siedział wewnątrz wiersza praw autorskich,
między danymi firmy a odnośnikami prawnymi. Odnośnik i jego parametry UTM się nie zmieniają.
Istniejący storefront się nie zmienia; aby przejąć ten układ, wprowadź tę samą zmianę w
components/Footer.tsx w projekcie storefrontu.
Własny storefront, który nie pochodzi od wzorca, serwuje pliki na tych samych zasadach:
GET /robots.txt?part=system odpowiada samymi jego generowanymi regułami, a
GET /llms.txt?part=base generowaną częścią llms.txt, oba jako text/plain z
X-Endora-Crawler-Files: 1, a kanał bez llms.txt odpowiada 404 z tym samym nagłówkiem; bez
nagłówka podgląd traktuje storefront jako nieaktualny. Odpowiada 503 zamiast plikiem, dopóki
unreadable w GET /api/v1/storefront/seo/crawler-files wskazuje policy lub
llmsTxtAdditional. Zobacz Dla własnego sklepu.
Jak dalece to sprawdzono: to, co mówi ta sekcja, pochodzi z dzienników zmian wydania, sprawdzonych
ze źródłami tego wydania — trasami i manifestem ustawień modułu seo, migracją platformy,
kontraktami, plikami wzorcowego storefrontu i przykładową konfiguracją nginx. W chwili pisania tej
sekcji żadna instancja nie została zaktualizowana z 0.105.0 poleceniem
pnpm run upgrade 0.106.0 i w żadnym storefroncie utworzonym we wcześniejszym wydaniu nie
wprowadzono trzech powyższych grup plików; kolejność kroków, wiersz dla proxy i zmiany w
storefroncie nie były wypróbowane.
Po aktualizacji do wydania 0.107.0
pnpm run upgrade 0.107.0 przenosi pakiety wydania z 0.106.0 i wykonuje jego migracje — piętnaście,
w tym jedną należącą do nowego modułu szablonów dokumentów. Wydanie przynosi trzy pakiety, których
wcześniej nie było — @endora-commerce/mod-document-templates, @endora-commerce/mod-admin-search
i @endora-commerce/llm-client — i jeden z nich nie jest opcjonalny: instancja, która
deklaruje @endora-commerce/mod-invoices, potrzebuje także
@endora-commerce/mod-document-templates, a krok setup aktualizacji sam go deklaruje i instaluje. Wydanie nie dodaje żadnej zmiennej środowiskowej. Dodaje
pięć uprawnień (platform.api_description.read, document_templates:read,
document_templates:write, admin_search:configure i cms.translate) i przenosi uprawnienia
importu i eksportu do modułów, do których należą dane.
Najwięcej zmienia się w tym, kto może korzystać z kanału sprzedaży. Każdy kanał ma teraz przełącznik Open to anonymous visitors, który po aktualizacji jest włączony, a zalogowany klient jest obsługiwany w kanale tylko wtedy, gdy jego organizacja jest do tego kanału przypisana. Migracja danych tworzy te przypisania dla istniejących organizacji, więc nic się nie zmienia dla klienta, który składał zamówienia tam, gdzie kupuje — ale przypisania warto przejrzeć, zanim instancja znów przyjmie ruch. Koszyki należą do kanału, w którym zostały napełnione, złożenie zamówienia sprawdza ceny koszyka, opis API nie jest już publiczny, a kilka ekranów i odpowiedzi się zmienia. Ta sekcja mówi, co zrobić i w jakiej kolejności, a potem — co działa inaczej, osobno dla każdego czytelnika: operatorów, klientów API i integratorów, autorów modułów i modułów nakładkowych oraz właściciela istniejącego storefrontu.
Co zrobić i w jakiej kolejności
Przed aktualizacją:
- Jeśli projektowano szablony faktur, zapisz, co zawierają. Szablony faktur przeniesiono do
nowego modułu Document templates i nic nie jest przenoszone automatycznie:
Migration20261017T075839InvoicesDropInvoiceTemplatesusuwa tabelęinvoice_templates, a PDF-y faktur — zarówno pobierane, jak i dołączane do e-maili — używają wbudowanego układu typu dokumentu faktury (dawnego szablonu ogólnego), dopóki operator nie ustawi szablonu faktury jako domyślnego w Document templates. Cofnięcie migracji odtwarza pustą tabelę, nie jej wiersze; jedyną kopią jest kopia zapasowa bazy danych. - Odczytaj
assets.soft_delete_retention_days— Soft-delete retention before hard-delete (days) w grupie Storage w Ustawieniach, domyślnie 30. Od tego wydania zasoby przeniesione do kosza są trwale usuwane przez cogodzinne zadanie, gdy od ich usunięcia minie tyle dni, a pierwsze uruchomienia po aktualizacji usuwają każdy zasób, który leży w koszu dłużej, do 500 na uruchomienie. Jeśli chcesz te zasoby zachować, wydłuż to ustawienie przed aktualizacją. Wartość mniejsza niż jeden jest odczytywana jako jeden dzień. - W instancji z więcej niż jednym kanałem sprzedaży odczytaj
quote_requests.expiry_days— Auto-expire pending after (days) na karcie Quote Requests w Ustawieniach — dla każdego kanału, który ma własną wartość. Zadanie wygaszania trzyma się teraz wartości kanału danego zapytania ofertowego, podczas gdy wcześniej stosowało wartość kanału domyślnego do wszystkich: przy pierwszych uruchomieniach kanał z niższą wartością niż domyślna wygasza zaległe zapytania, które były nieaktywne tak długo, a kanał ustawiony na0przestaje wygaszać.
Sama aktualizacja:
-
Uruchom
pnpm run upgrade 0.107.0.invoiceswymienia terazdocument_templateswśróddependenciesw swoim manifeście i jeśli instancja deklaruje@endora-commerce/mod-invoices, kroksetupaktualizacji dodaje@endora-commerce/mod-document-templatesdo głównegopackage.json, w wersji, w której zadeklarowana jest platforma, i instaluje go przed czymkolwiek innym. Jego pierwszy krok,endora generate, pochodzi już z właśnie zainstalowanego wydania i wypisuje wiersz:declared @endora-commerce/mod-document-templates 0.107.0 in package.json — invoices needs document_templates (dependencies)Zatwierdź zmienione
package.jsonipnpm-lock.yamlrazem z resztą aktualizacji. Jeśli kroksetupsię nie zakończył, uruchompnpm run setupponownie — kontynuuje od tego miejsca. Ręcznie musi go zadeklarować tylko instancja, w którejsetuppominięto albo która nie uruchamiagenerate:pnpm add -w -E @endora-commerce/mod-document-templates@0.107.0pnpm run setupBez tego modułu wdrożenie, które składa
invoices, odmawia uruchomienia i go wymienia:This deployment will not boot: it composes modules whose declarations it does not satisfy.document_templates — not shipped by this deployment, and it is needed:invoices — dependenciesReszta opisu Dodawanie modułu, który jest nowy w wydaniu obowiązuje — flagi i to, co uruchomić ponownie. Document templates startuje włączony i nie da się go wyłączyć, dopóki włączony jest
invoices. Pozostałe zależności dodane w wydaniu —quote_requestsodprice_listsitransactional_emails,invoicesiapi_keysodadmin_roles,assets_libraryodadmin_users— wskazują moduły, których nie da się wyłączyć i które każda instancja już deklaruje, więc nie wymagają niczego.Migracje, które wykonuje
setup, w kolejności ich znaczników czasu:Migracja Moduł Co robi Migration20261014T115049QuoteRequestsItemReferencePricequote_requestsTrzy kolumny dopuszczające nullwquote_request_itemsna cenę referencyjnąMigration20261014T115053QuoteRequestsNegotiationPoliciesquote_requestsDwie tabele, quote_request_org_policiesiquote_request_account_policiesMigration20261014T170531CatalogProductListSavedViewscatalogTabela product_list_saved_viewsMigration20261014T173236QuoteRequestsQuoteRequestListSavedViewsquote_requestsTabela quote_request_list_saved_viewsMigration20261016T131148PriceListsPriceMatrixEntriesprice_listsTabela price_list_matrix_entriesMigration20261016T172243PwaPushNotificationPreferencespwaTabela push_notification_preferencesMigration20261017T071754DocumentTemplatesInitdocument_templatesTabela document_templates, gdy moduł zostanie zadeklarowanyMigration20261017T075839InvoicesDropInvoiceTemplatesinvoicesUsuwa invoice_templates— zob. krok 1Migration20261017T142116QuoteRequestsQuoteRequestDocumentTemplatequote_requestsKolumna quote_requests.document_template_iddopuszczającanullMigration20261017T142121QuoteRequestsQuoteRequestEventPdfSentquote_requestsDopuszcza typ zdarzenia pdf-sentMigration20261018T074021CartsCartItemAgreedPricecartsTrzy kolumny dopuszczające nullwcart_itemsna uzgodnioną ilość i cenę z ofertyMigration20261018T101912CoreSalesChannelsAnonymousAccessplatforma sales_channels.anonymous_access boolean not null default trueMigration20261018T104644OrganizationsChannelAccessBackfillorganizationsPrzypisuje każdej organizacji jej kanały sprzedaży — zob. krok 7 Migration20261018T141739AssetsLibraryAssetVersionsPermalinksassets_libraryJedenaście kolumn w assets, dwie tabele na stałe linki i sprzątanie magazynuMigration20261019T043854CartsChannelBackfillcartsZapisuje koszyk bez kanału w domyślnym kanale systemowym Instancja, która deklaruje
@endora-commerce/mod-admin-search, nie wykonuje dla niego żadnej migracji: moduł nie ma własnych tabel.
Po aktualizacji, zanim instancja znów przyjmie ruch:
-
Nadaj ponownie uprawnienia do importu i eksportu. Każda encja na ekranie Import / Export wymaga teraz uprawnienia, które chroni te same wiersze w module, do którego należą, podczas gdy wcześniej każda encja wymagała
catalog:write:Encja Eksport Import products,categoriescatalog:readcatalog:writestockinventory:readinventory:writecustomerscustomers:read— ordersorders:read— Rola, która eksportowała klientów, zamówienia lub stany magazynowe albo importowała stany na podstawie
catalog:write, potrzebujecustomers:read,orders:read,inventory:readlubinventory:writenadanych w Admin roles (/admin-roles). Rola zcatalog:readmoże teraz eksportować produkty i kategorie. Ekran i jego pozycja w menu bocznym otwierają się dla roli z którymkolwiek kodem z tabeli, a karta każdej encji pokazuje tylko te kontrolki, na które rola pozwala. -
Nadaj
platform.api_description.readtym, którzy czytają opis API.GET /api/v1/_openapi.jsoniGET /api/v1/_docsodpowiadają tylko administratorowi, którego rola ma ten kod, albo kluczowi API wydanemu z tym kodem w zakresach; wszyscy inni dostają401(brak sesji administratora i klucza) albo403(sesja lub klucz bez kodu). Rola administratora platformy ma wszystkie uprawnienia i nie wymaga niczego. Skrypt lub proces budowania, który pobiera dokument — generator klienta, test kontraktu — potrzebuje klucza API z zakresemplatform.api_description.read, wydanego przez administratora, który ma ten kod, i wysyła go tak jak każdy klucz. W panelu administracyjnym dokument jest na nowym ekranie System → API description. -
Przejrzyj przypisania organizacji do kanałów sprzedaży i nie zamykaj żadnego kanału, dopóki storefront nie zostanie zaktualizowany. Po aktualizacji każdy kanał jest otwarty dla niezalogowanych odwiedzających, tak jak każdy kanał był wcześniej, a zalogowany klient jest obsługiwany w kanale tylko wtedy, gdy jego organizacja jest do tego kanału przypisana. Migracja
Migration20261018T104644OrganizationsChannelAccessBackfillprzypisała każdej organizacji domyślny kanał systemowy oraz każdy kanał, w którym ma co najmniej jedno zamówienie. W instancji z jednym kanałem sprzedaży obejmuje to wszystkich i nic się nie zmienia. W instancji z kilkoma kanałami organizacja, której klienci korzystają z innego kanału, nie składając w nim zamówienia — zarejestrowana w drugim sklepie i bez zamówień albo wysyłająca tam tylko zapytania ofertowe — dostaje teraz w tym kanale odmowę przy logowaniu, z komunikatem „To konto nie ma dostępu do tego sklepu.” Otwórz każdą taką organizację w panelu administracyjnym i zaznacz kanał w jej panelu Kanały sprzedaży. Potem zdecyduj dla każdego kanału, czy pozostaje otwarty dla niezalogowanych odwiedzających — Kanały sprzedaży i to, kto może z nich korzystać mówi, co oznacza każdy wybór — ale wyłączaj Open to anonymous visitors dopiero wtedy, gdy storefront ma pliki z części W istniejącym storefroncie, a w domyślnym kanale systemowym dopiero wtedy, gdy wiesz, jak docierają wywołania zwrotne płatności i przewoźników z modułów spoza wydania. -
W storefroncie, który obsługuje więcej niż jeden kanał sprzedaży przez nagłówek
X-Sales-Channel, a nie przezSALES_CHANNEL_HOST_MAP, zaktualizuj storefront, zanim klienci znów zaczną napełniać koszyki. Koszyk należy teraz do kanału, w którym jest napełniany, a storefront utworzony we wcześniejszym wydaniu wskazuje kanał tylko w tych wywołaniach backendu, które przekazują go jawnie; każde inne wywołanie backend przypisuje sam — według mapy hostów, a bez niej do domyślnego kanału systemowego. Do tego wydania nie miało to znaczenia dla koszyka, który istniał w kanale domyślnym bez względu na sklep. Od teraz koszyk kanału domyślnego i koszyk sklepu to dwa różne koszyki. -
Sprawdź, kto może nadawać jakie uprawnienia. Administrator nadaje teraz tylko uprawnienia, które ma jego własna rola — w roli, w roli konta administratora, w zakresach klucza API — a rolę tworzoną przez platformę lub moduł, w tym
platform_admin, może zmienić tylko posiadacz uprawnienia wieloznacznego*. Jeśli administrator z delegowanymi uprawnieniami zarządza rolami, użytkownikami lub kluczami API dla innych, nadaj jego roli uprawnienia, które on rozdaje, albo zostaw te zmiany posiadaczowi*.
W dowolnym momencie:
- Zdecyduj o powiadomieniach push dla ofert. W sklepie z włączonymi powiadomieniami push
zapytania ofertowe teraz je wysyłają: gdy administrator tworzy zapytanie dla klienta, modyfikuje,
zatwierdza lub anuluje je i gdy zapytanie wygasa. Wcześniej nie wysyłały ich nigdy, choć
przełącznik istniał. Jeśli ich nie chcesz, wyłącz tę kategorię ustawieniem
pwa.push_quote_status_enabledna ekranie PWA. - Zainstaluj
@endora-commerce/mod-admin-search, jeśli paleta poleceń ma znajdować rekordy — zamówienia, zapytania ofertowe, szanse sprzedaży, organizacje, klientów, produkty i kategorie. Moduł jest opcjonalny; bez niego paleta znajduje ekrany i akcje jak dotąd. Zadeklaruj go tak, jak opisuje Dodawanie modułu, który jest nowy w wydaniu; startuje włączony. - Zbuduj własne moduły ponownie z tym wydaniem. Kod, który implementuje lub konstruuje któryś z portów i kształtów wymienionych w części Dla autorów modułów i modułów nakładkowych, nie skompiluje się, dopóki nie zostanie zmieniony, a moduł, który odbiera wywołanie zwrotne dostawcy, ma deklarację do dodania.
- Zaktualizuj storefront zgodnie z częścią W istniejącym storefroncie. Bez tego zamówienie, strona produktu i logowanie działają jak wcześniej; zamknięte sklepy, ekran braku dostępu, wybór powiadomień push, blokada negocjacji cen i język PDF-a porównania — nie.
Dla operatorów
Kanały sprzedaży i to, kto może z nich korzystać
Każdy kanał sprzedaży ma przełącznik Open to anonymous visitors w formularzu kanału w Sales channels. Jest włączony w każdym istniejącym kanale i w każdym nowym.
- Włączony — każdy może przeglądać sklep; zalogowany klient może z niego korzystać, gdy jego organizacja jest przypisana do kanału.
- Wyłączony — sklep obsługuje wyłącznie zalogowanych klientów. Zaktualizowany storefront z każdej
strony kieruje niezalogowanego odwiedzającego na stronę logowania z komunikatem, że sklep jest dla
zalogowanych klientów biznesowych; nie dostaje on niczego z katalogu, cen ani treści, mapa strony
sklepu jest pusta, jego
robots.txtzabrania wszystkiego i nie wskazuje mapy strony, a jegollms.txtpodaje nazwę sklepu i mówi, że treść jest tylko dla zalogowanych klientów. Rejestracja firmy pozostaje na stronie logowania, a zarejestrowana tam organizacja otrzymuje ten kanał — więc przy automatycznej moderacji wchodzi każdy, kto zarejestruje firmę, a przy ręcznej nikt, dopóki operator nie zatwierdzi organizacji. Samodzielna rejestracja klienta indywidualnego (B2C) nie jest oferowana, a logowanie przez zewnętrznego dostawcę (Google, Microsoft) nie tworzy tam nowego konta. Lista kanałów oznacza zamknięty kanał etykietą Sign-in required. Pokazywanie cen niezalogowanym odwiedzającym to osobne ustawienie i ma znaczenie tylko w sklepie otwartym.
Backend stosuje regułę do każdego żądania spoza /api/v1/admin/, gdy jego kanał zostanie wyznaczony:
żądanie anonimowe w zamkniętym kanale dostaje 403 SALES_CHANNEL_SIGN_IN_REQUIRED, a żądanie
klienta — także administratora działającego jako klient — w kanale, do którego jego organizacja nie
jest przypisana, 403 SALES_CHANNEL_NOT_PERMITTED. Reguła nie dotyczy administratorów ani kluczy
API. Kilka tras jest osiągalnych w każdym aktywnym kanale, bo wywołujący musi do nich dotrzeć, aby
zostać klientem sklepu, albo nim nie jest: logowanie i wylogowanie, drugi składnik i logowanie przez
zewnętrznego dostawcę, reset hasła, weryfikacja e-maila, rejestracja firmy, zaproszenia, tożsamość
kanału, konfiguracja języków, słowniki czytane przez formularz rejestracji, polityka robotów i pliki
dla robotów, pobieranie plików produktowych i linki newslettera w e-mailach.
To, które organizacje mogą korzystać z kanału, ustawia się w organizacji, w nowym panelu
Kanały sprzedaży na jej stronie: odczyt z customers:read lub customers:manage, zmiana z
customers:manage. Organizacja zachowuje co najmniej jeden kanał. Każdy dodany lub usunięty kanał to
jeden wiersz w dzienniku audytu, a zmiana obowiązuje od razu na serwerze, który ją wprowadził, i na
każdym innym w ciągu 30 sekund. Klient, którego organizacja nie jest przypisana, dostaje odmowę przy
logowaniu — hasłem, drugim składnikiem lub przez zewnętrznego dostawcę — zanim powstanie sesja;
klient, któremu przypisanie usunięto w trakcie sesji, widzi w zaktualizowanym storefroncie ekran
Brak dostępu do tego sklepu z listą sklepów, z których może korzystać, każdy jako link, gdy
ustawienie Storefront URL kanału jest wypełnione.
Jaki kanał dostaje nowa organizacja. Firma zarejestrowana w sklepie dostaje kanał tego sklepu;
organizacja osobista utworzona przy rejestracji klienta indywidualnego lub logowaniu przez
zewnętrznego dostawcę dostaje kanał, w którym odwiedzający się zarejestrował; organizacja utworzona w
panelu administracyjnym dostaje kanały wybrane w formularzu; każda inna ścieżka tworzenia daje
domyślny kanał systemowy. Przyjęcie zaproszenia nie zmienia żadnego przypisania. New
organization (/organizations/new, przycisk na liście organizacji i akcja palety
new-organization, customers:manage) tworzy organizację w panelu administracyjnym — zduplikowany
NIP jest odrzucany, tak jak przy rejestracji.
Zanim zamkniesz domyślny kanał systemowy, dowiedz się, gdzie docierają wywołania zwrotne
dostawców. Powiadomienie bramki płatności, webhook przewoźnika czy adres powrotu od dostawcy
przychodzą bez sesji i zwykle bez kanału, więc są żądaniem anonimowym w domyślnym kanale
systemowym: jeśli moduł, który je odbiera, nie zadeklaruje trasy jako otwartej, po zamknięciu tego
kanału dostają 403, a powiadomienia dostawcy przestają docierać. POST /api/v1/payments/receive i
POST /api/v1/shipments/receive są wywoływane z sesją administratora lub kluczem API, których
reguła nie dotyczy; moduł bramki lub przewoźnika, który odbiera żądanie samego dostawcy, musi
zadeklarować tę trasę, jak opisuje część
Dla autorów modułów i modułów nakładkowych.
Zobacz Otwieranie i zamykanie sklepu dla niezalogowanych odwiedzających oraz Kanały sprzedaży na stronie modułu organizacji.
Koszyki, ceny i składanie zamówień
Koszyk należy do sklepu, w którym jest napełniany. Klient ma jeden aktywny koszyk na kanał
sprzedaży, wyceniany, objęty promocjami i sprawdzany pod kątem „tylko na zapytanie” w tym kanale,
podczas gdy wcześniej każdy koszyk był tworzony i wyceniany w domyślnym kanale systemowym. Token
koszyka niezalogowanego odwiedzającego wskazuje koszyk tylko w jednym sklepie, a napełnianie koszyka
w innym sklepie zaczyna nowy. Łączenie przy logowaniu łączy tylko koszyk anonimowy ze sklepu, w
którym klient się loguje. Złożenie zamówienia z koszykiem innego kanału jest odrzucane z
422 ORDER_SALES_CHANNEL_MISMATCH, zanim cokolwiek zostanie zarezerwowane lub zapisane, a koszyk
zostaje zachowany. Migracja Migration20261019T043854CartsChannelBackfill zapisała każdy koszyk bez
kanału w domyślnym kanale systemowym; jeśli klient miał już tam aktywny koszyk, koszyk bez kanału
pozostaje bez zmian i nie jest już osiągalny. Zobacz
Jeden koszyk na sklep.
Cena pozycji koszyka jest zawsze ceną dla jej bieżącej ilości, a złożenie zamówienia to sprawdza:
- dodanie kolejnych sztuk produktu, zmiana ilości, łączenie koszyka przy logowaniu i ponowne zamówienie zapisują cenę dla ilości wynikowej, więc próg ilościowy dotyczy zamawianej ilości, a ponowne zamówienie jest wyceniane według dzisiejszych cen;
- produkt, któremu cenę nadaje ani cennik, ani jego własna cena, jest odrzucany z
422 CART_PRODUCT_UNPRICED, podczas gdy wcześniej był dodawany po0.00; produkt sprzedawany tylko na zapytanie jest odrzucany z400 CART_PRODUCT_QUOTE_ONLYniezależnie od tego, czy jest w cenniku; - pozycja utworzona z zaakceptowanego zapytania ofertowego zachowuje uzgodnioną cenę tylko przy uzgodnionej ilości, a przy każdej innej jest wyceniana z cennika. Koszyk przekształcony z oferty przed tym wydaniem nie ma uzgodnionej ilości, więc jego pozycje są przy składaniu zamówienia wyceniane z cennika; ponowne przekształcenie oferty przywraca uzgodnione ceny;
- przy składaniu zamówienia pozycja, której zapisana cena nie jest już ceną, po jakiej można ją
zamówić, daje
409 CART_PRICE_CHANGED. Nic nie jest zapisywane, koszyk jest aktualizowany, a kupujący składa zamówienie ponownie po jego przejrzeniu. Istniejący storefront pokazuje tę odmowę na stronie błędu zamówienia z kodem błędu przed zdaniem; zaktualizowany pokazuje samo zdanie.
Zobacz Ceny pozycji.
Tworzenie zamówienia w panelu administracyjnym używa wybranego kanału.
POST /api/v1/admin/orders sprawdza produkty względem asortymentu kanału zamówienia, a nie żądania
administratora, i odrzuca nieistniejący kanał sprzedaży z 404, podczas gdy wcześniej składał
zamówienie w domyślnym kanale systemowym. Ekran Create order pokazuje Items i podsumowanie po
wybraniu klienta i kanału, dodaje produkty przez wspólną przeglądarkę produktów (do 200 naraz) lub
szybkie wyszukiwanie i mówi przy każdej pozycji, dlaczego produktu nie można zamówić: klient go nie
widzi albo nie ma go w asortymencie kanału. Podgląd zamówienia również odrzuca nieznany kanał z
404.
Uprawnienia i to, kto może je nadawać
- Import i eksport podlegają uprawnieniom modułów, do których należą dane — tabela jest w kroku 5.
- Opis API odczytuje się z
platform.api_description.read— krok 6. Nowy ekran System → API description (/api-description, dostępny też w palecie poleceń) pokazuje tytuł, wersję i liczbę punktów końcowych dokumentu, pobiera go jako JSON i otwiera interaktywną dokumentację pod własnym adresem API. Strona dokumentacji ładuje z jsDelivr przypiętą wersjęswagger-ui-dist,5.33.3, z sumą integralności dla każdego pliku, podczas gdy wcześniej ładowała wersję aktualną w danej chwili. - Administrator nadaje tylko to, co ma jego rola. Zapisanie roli z uprawnieniem, którego
wykonujący nie ma, albo zmiana roli, która już je ma, daje
403 ADMIN_ROLE_GRANT_EXCEEDEDz listą kodów; rolę tworzoną przez platformę lub moduł zmienia tylko posiadacz*(w przeciwnym razie409 ADMIN_ROLE_PROTECTED). Przypisanie roli daje403 ADMIN_ROLE_GRANT_EXCEEDED, jeśli wykonujący nie ma każdego uprawnienia, które ona nadaje, a administratora, którego rola ma coś, czego wykonujący nie ma, ten nie może edytować, ustawić mu hasła, dezaktywować ani usunąć (403 ADMIN_USER_OUTRANKS_ACTOR). Klucz API jest wydawany tylko z zakresami, które ma rola wydającego administratora (403 API_KEY_SCOPE_NOT_HELD). - Nowe uprawnienia.
document_templates:readidocument_templates:write(jednorazowy krok przy pierwszym uruchomieniu nadaje pierwsze każdej roli zinvoices:read, a każdej roli zinvoices:writetakże drugie, i zapisuje to w dzienniku audytu),admin_search:configure(po których polach szuka wyszukiwanie globalne, gdy ten moduł jest zainstalowany),cms.translate(akcja Translate w Page Builderze) iplatform.api_description.read.invoices:writema teraz etykietę Issue/correct invoices and configure invoicing.
Logowanie i konta
- Modułu
mfanie da się wyłączyć, dopóki administratorzy muszą używać drugiego składnika. Wyłączenie go na ekranie Modules daje409 MFA_DEACTIVATION_BLOCKED, dopóki włączone jestmfa.admin.totp_enforced, i niczego nie zmienia; najpierw wyłącz ten wymóg. Ponowne włączeniemfanigdy nie jest odrzucane. - Zmiana miejsca, do którego zapisane poświadczenie wysyła swoje sekrety, wymaga ponownego
wpisania sekretów. Zapisanie poświadczenia ze zmienionym polem punktu końcowego — Base URL
modelu LLM, host SMTP — daje
422 CREDENTIAL_SECRET_REENTRY_REQUIRED, chyba że każdy zapisany sekret rekordu zostanie wpisany ponownie w tym samym zapisie; formularz oznacza te pola, gdy tylko punkt końcowy zostanie zmieniony. Poświadczenie zapisane bez zmiany punktu końcowego zachowuje swoje sekrety jak wcześniej. - Logowanie klienta, drugi składnik klienta i prośby o reset hasła mają limity na konto, tak jak
logowanie administratora: 50 błędnych haseł w 30 minut z urządzenia, na którym konto się nie
logowało, 10 z takiego, na którym się logowało; 20 błędnych kodów drugiego składnika w 30 minut,
10 ze znanego urządzenia; 5 próśb o reset na godzinę. Powyżej limitu odpowiedź to
429 CUSTOMER_AUTHENTICATION_THROTTLEDzRetry-After. Udane logowanie ustawia plik cookieb2b_customer_device, który oznacza znane urządzenie — istniejący storefront go nie przekazuje, więc jego klienci są liczeni według pierwszego limitu, dopóki storefront nie zostanie zaktualizowany. Prośba o nowy link resetujący unieważnia wcześniejsze, niewykorzystane. - Logowanie przez zewnętrznego dostawcę podlega regułom logowania hasłem, w storefroncie i w panelu administracyjnym: kończy się tylko w przeglądarce, która je rozpoczęła, konto z drugim składnikiem trafia na ekran drugiego kroku zamiast zostać zalogowane, a zablokowane konto klienta dostaje odmowę z tym samym zdaniem co przy logowaniu hasłem. Sesja klienta, którego konto jest zablokowane, usunięte lub nie istnieje, jest traktowana jak wylogowana.
- Błędny kod lub hasło na ekranach drugiego składnika po zalogowaniu nie wylogowuje już
administratora. Potwierdzenie konfiguracji, wyłączenie drugiego składnika i ponowne generowanie
kodów odzyskiwania odpowiadają na błędny kod
403 MFA_INVALID_CODE, a na błędne hasło403 CURRENT_PASSWORD_INVALID; wcześniej odpowiadały401, które panel administracyjny traktuje jak wygasłą sesję.
Biblioteka mediów
- Nie można przesyłać plików HTML, XHTML, XML (w tym XSL) ani JavaScript, niezależnie od
assets.allowed_file_types:415 ASSET_UPLOAD_TYPE_NOT_ALLOWED, według zadeklarowanego typu i rozszerzenia. SVG jest nadal przyjmowany i zapisywany jakoimage/svg+xml. Plik tych typów przesłany wcześniej pozostaje w bibliotece i jest serwowany do pobrania. - Pliki są serwowane ze stałym nagłówkiem polityki treści i
X-Content-Type-Options: nosniff; obrazy rastrowe, SVG, wideo, audio, PDF i zwykły tekst są wyświetlane w przeglądarce, każdy inny typ jest pobierany. Prywatny zasób w S3 lub GCS jest przekierowywany do magazynu tylko z ważnym podpisanym linkiem, tak jak zasób lokalny już był (w przeciwnym razie403 ASSET_ACCESS_DENIED). - Plik zasobu można podmienić w miejscu, z zachowaniem identyfikatora, folderu, etykiety,
widoczności, każdego odwołania i każdego wydanego już adresu; podmieniony plik można przywrócić
jako poprzednią wersję przez
assets.soft_delete_retention_days. Zasób może mieć stały link, serwowany pod/assets/file/<permalink>. Oba są w panelu zasobu, za uprawnieniemassets.write; biblioteka jest też w palecie poleceń jako Assets library. - Osadzane adresy zasobów mają
?v=<wersja treści>, więc podmieniony plik zostaje pobrany, a osadzenie może być buforowane przez rok;GET /assets/file/<id>jest teraz wysyłany zCache-Control: public, no-cache. Strona CMS zbuforowana przed podmianą może przez maksymalnie pięć minut serwować poprzednią wersję. - Zasoby w koszu są trwale usuwane po
assets.soft_delete_retention_days— krok 2. Każde uruchomienie zadania zapisuje jeden wiersz dziennikaassets_library.retention.sweep.
Zobacz Które adresy są stałe oraz Przechowywanie i trwałe usuwanie.
Dokumenty i PDF-y
- Document templates (
document_templates) to miejsce, w którym projektuje się układy PDF, w Page Builderze, dla typu dokumentu i kanału sprzedaży, po jednym układzie na język. Obsługuje fakturę, porównanie produktów i zapytanie ofertowe. Ekrany szablonów faktur przeniesiono tam:/invoices/templatesprzekierowuje do nich, a PDF faktury wysyłany e-mailem jest teraz generowany dokładnie tak jak pobierany. Zobacz Document templates. - Ekran zapytania ofertowego oferuje Export PDF i Send PDF to customer w każdym stanie,
dopóki szablony dokumentów są włączone. Wysyłka przesyła PDF na maksymalnie pięć adresów przez nowy
e-mail transakcyjny
quote_request_pdf, edytowalny i wyłączalny jak każdy inny, i pojawia się w historii oferty. - PDF porównania korzysta z szablonu, w języku, w którym kupujący przegląda sklep, jeśli kanał go oferuje: kupujący przeglądający sklep po polsku dostaje teraz polski PDF.
Zapytania ofertowe
- Każda pozycja oferty zapisuje cenę referencyjną — ile klient zapłaciłby w zwykłym zamówieniu w kanale zapytania — pokazywaną w panelu administracyjnym obok propozycji klienta i ceny uzgodnionej. Koszyk przekształcony w zapytanie ofertowe nie zapisuje już swoich cen jako propozycji klienta.
- Blokada negocjacji cen dla klienta. W organizacji (domyślnie dozwolone) oraz, jako nadpisanie,
na koncie klienta administrator decyduje, czy klienci mogą proponować ceny. Utworzenie lub edycja
szkicu z proponowaną ceną, gdy właścicielowi zapytania nie wolno, jest odrzucana z
422 RFQ_PRICE_PROPOSAL_NOT_ALLOWED. Panele wymagającustomers:readdo odczytu icustomers:managedo zmiany. - Czytelna historia na ekranie zapytania ofertowego, łącząca zapisy jednej decyzji i wskazująca, kto działał.
- Przedstawiciel handlowy spoza organizacji zapytania dostaje
404, jak dla nieistniejącego zapytania, podczas gdy wcześniej dostawał403. - Najwyżej 200 pozycji przy tworzeniu lub edycji szkicu przez klienta (powyżej
400). Klient może wielokrotnie edytować oczekujący szkic, dopóki administrator go nie zmodyfikuje, do 30 edycji na minutę na zapytanie.
Ceny, katalog i listy
- Cenniki zyskują macierz cen (karta Price matrix cennika): jeden wpis wycenia produkt, kategorię, wartość atrybutu, kategorię z wartością atrybutu albo wszystkie produkty, opcjonalnie dla organizacji i przedziału ilości, ceną stałą lub rabatem procentowym. Wybór według atrybutu używa atrybutów oznaczonych Price list rules. Zobacz Cenniki.
- Dodawanie produktów do cennika otwiera wspólną przeglądarkę produktów i dodaje do 200 produktów w jednym kroku. Systemowy cennik Default nadal nie przyjmuje produktów w ten sposób.
- Odczyty katalogu w storefroncie mogą być wyceniane w walucie kupującego, jeśli kanał ją obsługuje. W walucie innej niż domyślna waluta kanału produkt, któremu cenę nadaje tylko jego własna cena, nie ma ceny na liście, zamiast pokazywać własną cenę w niewłaściwej walucie.
- Zapisane widoki na liście produktów i liście zapytań ofertowych, a na liście zamówień zapis i usuwanie przez okna dialogowe zamiast monitów przeglądarki. Dwa widoki jednego administratora na jednej liście nie mogą różnić się tylko wielkością liter.
- Nowo utworzony produkt jest od razu wyszukiwalny, bez pierwszej edycji.
- Listy CMS — Pages, Blocks, Templates, Hooks — są w palecie poleceń.
Powiadomienia, newsletter i AI
- Powiadomienia push o statusie. Dwa ustawienia,
pwa.push_order_status_enabledipwa.push_quote_status_enabled(domyślnie włączone, globalnie i dla kanału), włączają obie kategorie; klient wybiera dla swojego konta w zaktualizowanym storefroncie. Powiadomienia o zamówieniach są w języku kanału, nazywają status tak jak sklep i otwierają zamówienie. Powiadomienia o ofertach zaczynają być wysyłane — krok 10. - Śledzenie otwarć i kliknięć newslettera włączają dwa nowe ustawienia,
newsletter.tracking.opens_enabledinewsletter.tracking.clicks_enabled, domyślnie wyłączone. Gdy są wyłączone, kampanie są wysyłane bez śledzenia, jak wcześniej. - Page Builder potrafi tłumaczyć płótno karty języka lub zaznaczenie modelem LLM skonfigurowanym
w Settings → Page Builder (
cms.page_builder.translation.enabled, domyślnie wyłączone), za uprawnieniemcms.translate. Każde żądanie jest zapisywane w dzienniku audytu. - Konfiguracje LLM obsługują DeepSeek i dowolny punkt końcowy zgodny z OpenAI przez nowego
dostawcę OpenAI-compatible (custom endpoint); każdy Base URL modelu LLM musi być bezwzględnym
adresem
http://lubhttps://.
Wszystko inne, co widzi operator
- Globalne wyszukiwanie w panelu administracyjnym, gdy zainstalowano
@endora-commerce/mod-admin-search(krok 11): paleta szuka rekordów od minimalnej długości (admin_search.minimum_query_length, domyślnie 3), a każdy typ podlega widoczności swojej listy w panelu;/global-searchpokazuje pełne wyniki. Zobacz Admin search. - Ekran ustawień nie pokazuje już wartości jednego kanału sprzedaży pod nagłówkiem innego, gdy lista jeszcze się wczytywała w chwili zmiany „Editing for”.
- Menu boczne pokazuje numer wydania w osobnej sekcji, pod logo.
- Podglądy e-maili w edytorze e-maili i w podglądzie kampanii newslettera są rysowane w ramkach, które nie uruchamiają skryptów.
order.cancelled.v1jest emitowane przy anulowaniu zamówienia, więc subskrybent napisany pod to zdarzenie teraz działa.
Dla klientów API i integratorów
- Opis API wymaga uprawnienia.
GET /api/v1/_openapi.jsoniGET /api/v1/_docswymagają sesji administratora, którego rola maplatform.api_description.read, albo klucza API z tym zakresem:401 UNAUTHORIZEDbez żadnego z nich, także z sesją klienta,403 FORBIDDENbez kodu. Obie odpowiedzi mająCache-Control: private, no-store.GET /api/v1/admin/api-descriptionserwuje ten sam dokument w przestrzeni administracyjnej.apiKeyScopeSchemaprzyjmuje nowy zakres. - Dostęp do kanału sprzedaży. Każde żądanie storefrontu jest rozstrzygane po wyznaczeniu kanału:
403 SALES_CHANNEL_SIGN_IN_REQUIREDdla żądania anonimowego w zamkniętym kanale,403 SALES_CHANNEL_NOT_PERMITTEDdla klienta, którego organizacja nie jest przypisana, oba zdetails.channelCode.POST /api/v1/auth/customer/loginodpowiada tym drugim po poprawnym haśle (błędne nadal daje najpierw401) i nie ustawia pliku cookie.GET /api/v1/storefront/sales-channelzawieraanonymousAccess, a noweGET /api/v1/storefront/sales-channel/accessmówi wywołującemu, czy może korzystać ze sklepu, a klientowi z odmową — z których sklepów może; nigdy nie odmawia. Panel administracyjny:GET/PUT /api/v1/admin/organizations/:id/sales-channels,GET /api/v1/admin/organizations/sales-channel-options,POST /api/v1/admin/organizations;anonymousAccessw kształtach administracyjnych kanału sprzedaży (przy tworzeniu domyślnietrue).GET /api/v1/storefront/seo/crawler-fileszawiera opcjonalneanonymousAccess— brak traktuj jakotrue. - Koszyki per kanał. Każda trasa koszyka storefrontu dotyczy koszyka kanału żądania; pozycja
koszyka innego kanału daje
404. Złożenie zamówienia z koszykiem innego kanału daje422 ORDER_SALES_CHANNEL_MISMATCH. - Złożenie zamówienia sprawdza ceny.
409 CART_PRICE_CHANGEDzdetails.lines—productId,variantId,quantity,previousUnitPrice,currentUnitPrice(null, gdy pozycji nie można już zamówić) icurrency. Odczytaj koszyk ponownie i złóż zamówienie jeszcze raz. Dodanie produktu bez ceny daje422 CART_PRODUCT_UNPRICED. - Zamówienia w panelu administracyjnym.
POST /api/v1/admin/ordersiPOST /api/v1/admin/orders/previewodpowiadają na nieznanysalesChannelId404 NOT_FOUND. Podgląd podaje dwa nowe powody niedostępności pozycji,PRODUCT_NOT_VISIBLE_TO_CUSTOMERiPRODUCT_NOT_IN_SALES_CHANNEL, po jednym na pozycję w kolejności, jaką podajeADMIN_ORDER_PREVIEW_MESSAGE_PRECEDENCE;messages[].codepozostaje ciągiem znaków, więc nieznany kod traktuj jako ogólne „niedostępne”. - Import i eksport.
GET /api/v1/admin/import-export/entitieswymienia tylko to, co wywołujący może eksportować lub importować, każdą pozycję zcanExporticanImport; trasy eksportu i importu odpowiadają403bez uprawnienia danej encji, a405przy imporcie encji tylko do eksportu, którą wywołujący może eksportować. - Zmienione kody odpowiedzi.
- Trasy samoobsługi drugiego składnika (
…/activate,…/disable,…/recovery-codes/regeneratepod/api/v1/admin/account/mfai/api/v1/account/mfa) odpowiadają403 MFA_INVALID_CODEi403 CURRENT_PASSWORD_INVALID, podczas gdy wcześniej odpowiadały401. Wyzwania przy logowaniu zachowują401. - Przedstawiciel handlowy spoza organizacji zapytania ofertowego dostaje
404na każdej administracyjnej trasie zapytań ofertowych. - Żądanie ograniczone limitem odpowiada
429 RATE_LIMITEDw standardowej kopercie błędu, zRetry-After, podczas gdy wcześniej odpowiadało500 INTERNAL. - Treść, której serwer nie potrafi odczytać, daje
400(pusta zContent-Type: application/jsonlub niepoprawna),413(ponad limit rozmiaru) albo415(brak parsera dla typu), każda zVALIDATION_FAILED, podczas gdy wcześniej dawała500. PATCH /api/v1/admin/cms/hooks/:id/attachments/:blockIddla bloku, który nie jest dołączony, odpowiada404 CMS_BLOCK_NOT_FOUND.POST /api/v1/admin/assetsz dwiema częściami plikowymi odpowiada201dla pierwszej z nich.
- Trasy samoobsługi drugiego składnika (
- Nowe odmowy.
415 ASSET_UPLOAD_TYPE_NOT_ALLOWEDprzy przesyłaniu HTML, XHTML, XML lub JavaScript;409 MFA_DEACTIVATION_BLOCKEDprzy wyłączaniumfa, gdy wymuszony jest TOTP administratorów;422 CREDENTIAL_SECRET_REENTRY_REQUIRED(details.fields) przy zmianie punktu końcowego poświadczenia bez jego sekretów;403 ADMIN_ROLE_GRANT_EXCEEDED,409 ADMIN_ROLE_PROTECTED,403 ADMIN_USER_OUTRANKS_ACTORi403 API_KEY_SCOPE_NOT_HELDz reguły nadawania;429 CUSTOMER_AUTHENTICATION_THROTTLEDi503 CUSTOMER_AUTHENTICATION_UNAVAILABLEprzy logowaniu klienta;422 RFQ_PRICE_PROPOSAL_NOT_ALLOWED; oraz400dla ponad 200 pozycji przy tworzeniu lub edycji szkicu zapytania ofertowego przez klienta. - Przyjmowanie zdarzeń analitycznych.
POST /api/v1/analytics/eventszapisuje tożsamość wyznaczoną przez serwer — zalogowanego klienta, kanał żądania — i ignorujecustomerAccountId,organizationIdisalesChannelIdw treści, które nadal przechodzą walidację. Limit to 60 partii na minutę na adres klienta. - Linki śledzące newslettera to
GET /api/v1/newsletter/open.gif?token=…iGET /api/v1/newsletter/click?token=…&u=…. Dawne formy ze ścieżką, do których podpisany token był za długi, zostały usunięte. - Adresy zasobów.
urlistorageUrlzasobu publicznego zawierają?v=<contentVersion>; stałą formą jest nowefileUrl. Podsumowania i szczegóły zyskująfileUrl,permalink,permalinkUrlicontentVersion; szczegóły zyskująpreviousVersion,retentionDaysireplaceable. Nowe trasy:POST /api/v1/admin/assets/:id/replace,POST …/previous-version/restore,PUT/DELETE …/permalink,GET /api/v1/admin/assets/folders/:folderId/permalink-count. - Usunięte: API szablonów faktur.
/api/v1/admin/invoice-templatesi jego trasy zostały usunięte; użyj/api/v1/admin/document-templateszdocumentType: 'invoices.invoice'. - Nowe w tym wydaniu, wszystko addytywne: historia zapytania ofertowego
(
GET /api/v1/admin/quote-requests/:id/history), negocjacja cen (…/organizations/:id/quote-price-negotiation,…/customers/:id/quote-price-negotiation,GET /api/v1/quote-requests/price-negotiation), PDF zapytania ofertowego (/api/v1/admin/quote-requests/:id/pdf,…/pdf/send,…/document-templates,…/document-template), macierz cen (/api/v1/admin/price-lists-engine/:id/matrix) i dodawanie zbiorcze (…/:id/products/bulk-add), zapisane widoki (/api/v1/admin/catalog/products/list-views,/api/v1/admin/quote-requests/list-views), preferencje push (GET/PUT /api/v1/storefront/pwa/preferences), wyszukiwanie globalne (/api/v1/admin/global-search…), tłumaczenie w Page Builderze (/api/v1/admin/cms/page-builder/translat…),?currency=w odczytach katalogu orazsalesChannelIdwGET /api/v1/admin/products/:id/resolved-price, który teraz odpowiada404dla kupującego, który nie istnieje albo którego przedstawiciel handlowy nie może widzieć. - Zapisane widoki zamówień. Pozycje mają
ownedByCallericanModify; nazwa zajęta przez inny widok właściciela daje409 ORDER_SAVED_VIEW_NAME_TAKEN;PATCH, który niczego nie zmienia, niczego nie zapisuje; identyfikator, który nie jest UUID, daje404; brak sesji administratora daje401.
Dla autorów modułów i modułów nakładkowych
Zadeklaruj jako otwartą dla reguły dostępu do kanału każdą trasę, do której odwiedzający dociera przed zalogowaniem albo która przychodzi bez kanału storefrontu. Dotyczy to tras logowania, rejestracji i odzyskiwania konta, linków w e-mailach, pobierania plików produktowych i — przypadku najłatwiejszego do przeoczenia — wywołań zwrotnych dostawców płatności i przewoźników oraz adresów powrotu od dostawców:
app.post(
'/api/v1/payments/acme/notify',
{ config: { salesChannelAccess: 'open' } },
async (request, reply) => {
// Still resolved to a channel, and still behind this route's own guards.
},
);
Bez tego trasa dostaje 403 w chwili, gdy operator zamknie kanał, do którego jest przypisywana — dla
wywołania zwrotnego jest to domyślny kanał systemowy. Opcja jest otypowana w FastifyContextConfig
Fastify przez barrel ./http platformy. Pomija tylko krok dostępu: trasa nadal jest przypisywana do
kanału i zachowuje własne warunki dostępu. Nie deklaruj jej na niczym, co pokazuje lub zmienia
katalog, ceny, koszyk lub zamówienia sklepu. SalesChannelAccessPort (salesChannelAccessPort,
./kernel) rozstrzyga regułę dla ścieżki, która nie ma jeszcze sesji. Zobacz
Dostęp do kanału: trasy, które muszą pozostać otwarte.
Zmiany, po których kod się nie kompiluje albo nie składa. Kod, który implementuje, konstruuje lub wywołuje którykolwiek z poniższych elementów, wymaga zmiany:
| Co | Zmiana |
|---|---|
CartCustomerContext (@endora-commerce/contracts) | salesChannelId jest wymagane: każdy wywołujący CartReadPort / CartWritePort wskazuje kanał, o którego koszyk chodzi |
PersonalOrganizationProvisionInput (@endora-commerce/mod-organizations/ports) | wymagane salesChannelId: string | null |
CatalogProductReadPort | nowa wymagana metoda listProductIdsByAttributeValue({ key, value, match }) |
OrderReadPort | nowa wymagana metoda findStatusDefinition(code) |
DocumentRenderPort | nowa wymagana metoda listChoices({ documentType, salesChannelId }) |
CartPlacementApplyPort (@endora-commerce/mod-carts/ports) | nowe findPriceDrift i refreshLinePrices |
CartService | nowy wymagany argument konstruktora ProductDisplayModePort po channelMembership; getOrCreateForAnon(em, token, salesChannelId) zwraca Cart | null |
CartsDeps | cartCouponService, cartConversionService, pushLineToShoppingList i appendShoppingListToCart są wymagane |
PriceListTargetReads | wymagane catalogAttributeRead — dla ręcznie budowanego PriceListService lub PricingService |
MfaModuleOptions | wymagane customerChannelAccess |
AnalyticsIngestService.ingest | wymagany context (IngestContext) |
customerOrgResolver (mod-customer-accounts) | odpowiada { organizationId, active } lub null; aktualizuj razem z nim mod-auth |
customerSocialLoginPort | resolveByEmail odpowiada { id, organizationId, blocked }, autoCreate(email, { salesChannelId }) odpowiada { id, organizationId } |
AssetSummary, AssetDetail, AssetRecord | nowe wymagane pola (fileUrl, permalink, permalinkUrl, contentVersion; retentionDays, replaceable) dla kodu, który je buduje |
OrderSavedView | wymagane ownedByCaller i canModify dla kodu, który go buduje |
buildServer | nie montuje już tras opisu API; w procesie czytaj app.apiDescription(), a registerOpenApiRoutes usunięto |
Nowe porty i punkty styku. BuyerLinePricePort (buyerLinePricePort, z price_lists) — cena
jednostkowa netto, jaką kupujący zapłaciłby za każdą pozycję w kanale; ProductDisplayModePort
(productDisplayModePort); MfaDeactivationGuardPort; CustomerAuthenticationThrottlePort oraz,
w ./kernel, AuthenticationAttemptThrottle; AdminSearchProviderRegistryPort
(adminSearchProviderRegistry) — dodaj dostawcę z haka startowego, który tylko wnosi wkład, aby
rekordy modułu dało się znaleźć; DocumentTypeRegistryPort (documentTypeRegistry) i
DocumentRenderPort (documentRenderPort) — rejestracja typu dokumentu i jego generowanie;
AdminRolePort.upsertByCode(input, { grantor }) — przekaż grantor dla zapisu wykonywanego w
imieniu administratora; createLlmAdapter z @endora-commerce/llm-client, przeniesiony z
mod-prompt-actions. BlockDefinition zyskuje opcjonalne ścieżki translatable, a blok bez nich
nigdy nie jest wysyłany do modelu. Pole poświadczenia, które wskazuje, dokąd wysyłane są sekrety,
deklaruje endpoint: true, chyba że jest polem format: 'url', które liczy się domyślnie.
order.cancelled.v1 jest emitowane, a jego treść rośnie z { orderId } do orderId,
organizationId, salesChannelId, from, actor, reason i businessId. Pozostaje na
wewnątrzprocesowej szynie zdarzeń; webhooki wychodzące nadal dostarczają anulowanie jako
order.status_changed.v1.
Admin kit. ProductBrowserDialog, ProductBrowserTrigger i productSelectability;
ModalDialog i SavedViewsControl; AssetUploader zyskuje upload i accept. Ich teksty są w
pakiecie core modułu @endora-commerce/mod-i18n w tym wydaniu.
Zależności równorzędne. mod-quote-requests deklaruje @fastify/rate-limit (^10), które host
platformy już rejestruje; mod-cms i mod-prompt-actions deklarują @endora-commerce/llm-client,
który pnpm instaluje razem z nimi, tak jak inne zależności równorzędne.
W istniejącym storefroncie
Istniejący storefront nie zmienia się przez aktualizację. endora new storefront kopiuje
wzorzec raz, a potem projekt należy do Ciebie; aktualizacja przenosi jego pakiety, których poniższe
pliki potrzebują w wersji 0.107.0. Dopóki jego pliki nie zostaną zaktualizowane, działa z
zaktualizowanym backendem w sklepie otwartym dla niezalogowanych odwiedzających: katalog, koszyk,
zamówienie i logowanie działają jak wcześniej, w granicach podanych przy każdej grupie. To, czego
nie robi, opisano przy każdej grupie. Storefront do kopiowania utwórz tak, jak opisuje
Bloki modułów w istniejącym storefroncie, i przejmuj grupy w tej
kolejności. Tam, gdzie edytowano plik, nanieś jego różnicę ręcznie; diff -ru między Twoim
storefrontem a nowym pokazuje każdą różnicę obok Twoich własnych zmian. Kilka plików należy do więcej
niż jednej grupy — nową wersję weź raz.
1. Dostęp do kanału sprzedaży — wymagany, zanim jakikolwiek kanał zostanie zamknięty dla
niezalogowanych odwiedzających, oraz w storefroncie, który obsługuje kilka kanałów przez
X-Sales-Channel. Storefront wskazuje swój kanał w każdym wywołaniu backendu, kieruje
odwiedzającego zamkniętego sklepu na stronę logowania, pokazuje odmowę logowania i ekran braku
dostępu oraz przestaje oferować rejestrację klienta indywidualnego w zamkniętym sklepie. Bez tego
zamknięty sklep odpowiada na wywołania stron storefrontu 403, a klient z odmową nie ma ekranu,
który mówi dlaczego.
- nowe:
components/ChannelAccessDenied.tsx,lib/api/channel-headers.ts,lib/auth/sign-in-errors.ts,lib/channel/channel-header.ts,lib/channel/channel-identity.ts,lib/channel/closed-store.ts,lib/actions/sign-out.ts,lib/dictionary/browser.ts,lib/dictionary/types.ts,components/account/SignOutButton.tsx,lib/cart-action-failure.ts; - zmienione:
middleware.ts— oznaczax-sales-channelkanałem, który backend wyznacza dla tego storefrontu, gdy nie zrobiło tego proxy, i kieruje odwiedzającego zamkniętego sklepu do logowania;app/layout.tsx,app/page.tsx,app/sitemap.ts,app/globals.css;app/(auth)/login/page.tsx,app/(auth)/login/mfa/page.tsx,app/(auth)/register-customer/page.tsx;app/(catalog)/c/[slug]/page.tsx,app/(catalog)/catalog/page.tsx,app/(catalog)/p/[slug]/page.tsx,app/(catalog)/search/page.tsx;app/compare/page.tsx,app/compare/share/[token]/page.tsx;app/pwa/subscriptions/route.ts; komponentyCompareToggle,ComparisonTable,Header,MfaCodeForm,ProductCard,ProductCardActions,ProductGrid,SearchAutocomplete,SharedComparisonTable,SocialLoginButtons,analytics/AnalyticsProvider,analytics/GtmProvider,inventory/NotifyWhenAvailableDialogimobile/MobileSearchOverlay; oraz wlib/:analytics/gtag.ts,analytics/gtm/dataLayer.ts,api/cart.ts,api/client.ts,api/comparisons.ts,api/mutations.ts,api/newsletter.ts,api/pwa-server.ts,api/rfq.ts,api/sales-channel.ts,api/search.ts,api/speculation-rules.ts,dictionary/DictionaryProvider.tsx,dictionary/client.ts,i18n/messages.tsiserver-context.ts. Ta sama zmiana dotykalib/seo/crawler-policy.tsilib/seo/crawler-file-response.ts, które istnieją tylko w storefroncie, który przejął grupy SEO z wydania 0.106.0; grupa 2 je zastępuje.
Wzorzec pilnuje statycznym testem, by każde wywołanie backendu przechodziło przez pomocnika kanału;
jeśli utrzymujesz testy storefrontu z test/, skopiuj je razem z plikami.
2. Pliki dla robotów w zamkniętym sklepie — zakłada trzy grupy SEO z wydania
0.106.0 i grupę 1 powyżej. Zastąp lib/seo/crawler-policy.ts,
lib/seo/crawler-file-response.ts, lib/seo/robots-txt.ts, lib/seo/llms-txt.ts i
lib/seo/llms-txt-base.ts oraz dodaj nową funkcję renderClosedStoreRobotsTxt do
lib/seo/robots-system-rules.ts — reszta tego pliku, ROBOTS_SYSTEM_RULES z ewentualnymi Twoimi
regułami, się nie zmienia. W zamkniętym sklepie robots.txt zabrania wtedy wszystkiego i nie wskazuje
mapy strony, a llms.txt podaje nazwę sklepu i mówi, że treść jest tylko dla zalogowanych klientów;
własne instrukcje i dodatkowa treść operatora są zachowane i wracają po otwarciu sklepu. Pliki
otwartego sklepu się nie zmieniają.
3. Limity logowania klientów. lib/session.ts, lib/api/auth.ts, lib/api/mfa.ts i
app/(auth)/password-reset/request/page.tsx: storefront przekazuje plik cookie b2b_customer_device
przy logowaniu, drugim kroku i prośbie o reset oraz zachowuje ten, który ustawia backend. Bez nich
każda próba jest liczona według limitu urządzenia, na którym konto się nie logowało.
4. Wybór powiadomień push i wylogowanie. app/(account)/account/page.tsx,
app/(account)/layout.tsx, components/account/SignOutButton.tsx,
components/pwa/PushNotificationsToggle.tsx (nowy), components/pwa/PushPreferencesForm.tsx
(nowy), components/pwa/PushOptIn.tsx, lib/api/pwa.ts i lib/api/pwa-server.ts. Profil konta
zyskuje wybór powiadomień o zamówieniach i ofertach oraz przełącznik wyłączający powiadomienia;
wylogowanie zwalnia subskrypcję push przeglądarki. Bez nich klient dostaje obie kategorie, które
oferuje operator.
5. Blokada negocjacji cen w szkicu zapytania ofertowego. app/(commerce)/quote-request/actions.ts,
app/(commerce)/quote-request/page.tsx, components/rfq/RfqDraftView.tsx i lib/api/rfq.ts. Bez
nich klient z zablokowanymi propozycjami widzi pole propozycji i dostaje 422 przy wysłaniu
propozycji.
6. Mniejsze poprawki. app/(commerce)/checkout/page.tsx — strona błędu pokazuje zdanie
backendu, także dla CART_PRICE_CHANGED, bez kodu na początku; app/(catalog)/p/[slug]/page.tsx,
lib/cart-action-failure.ts i lib/i18n/messages.ts — strona produktu opisuje błędy koszyka w
języku kupującego; app/compare/page.tsx, components/ComparisonTable.tsx i
lib/api/comparisons.ts — PDF porównania jest zamawiany w wyświetlanym języku i z wyświetlanymi
wierszami; lib/api/catalog.ts — odczyty katalogu w wybranej walucie; components/Breadcrumbs.tsx
i app/(catalog)/p/[slug]/page.tsx — dane strukturalne produktu i ścieżki nawigacji renderowane
przez JsonLd.
Następnie uruchom pnpm run typecheck, pnpm test i pnpm run build i usuń katalog, z którego
kopiowano. Własny storefront, który nie wywodzi się ze wzorca, musi zrobić trzy rzeczy, aby
działać z zamkniętym sklepem: wskazywać swój kanał w każdym wywołaniu backendu, także z
przeglądarki; kierować odwiedzającego do logowania przy 403 SALES_CHANNEL_SIGN_IN_REQUIRED; oraz
pokazywać 403 SALES_CHANNEL_NOT_PERMITTED, odczytując z
GET /api/v1/storefront/sales-channel/access sklepy, z których klient może korzystać.
Jak dalece to sprawdzono: to, co mówi ta sekcja, pochodzi z opisów zmian (changesetów) wydania,
sprawdzonych ze źródłami tego wydania — manifestami, migracjami, trasami i historią wzorcowego
storefrontu między v0.106.0 a 0.107.0. W chwili pisania tej sekcji żadna instancja nie została
zaktualizowana z 0.106.0 poleceniem pnpm run upgrade 0.107.0, do żadnej instancji deklarującej
mod-invoices nie dodano po aktualizacji mod-document-templates i w żadnym storefroncie
utworzonym we wcześniejszym wydaniu nie wprowadzono powyższych grup plików; kolejność kroków i
zmiany w storefroncie nie były wypróbowane.
Po aktualizacji do wydania 0.108.0
pnpm run upgrade 0.108.0 przenosi pakiety wydania z 0.107.0 i wykonuje jego migracje —
dwadzieścia osiem, z czego dwanaście dotyczy nowego modelu cen i jego edytora, trzy poszerzają
ceny jednostkowe do czterech miejsc po przecinku, a trzy zapisują, jak naliczono VAT i rabat.
Wydanie zastępuje
sposób przechowywania i wyliczania cen w jednym kroku, bez okresu zgodności: cenniki wcześniejszych
wydań, z ich regułami stosowania (Application rule), listami produktów i przedziałami ilościowymi,
stają się cennikami w nowym modelu, a produkt nie ma już własnej ceny. Ceny bazowe, które
instancja naliczała z cennika Default, zostają zachowane; każdy inny cennik aktualizacja
usuwa i nic go nie przenosi — jedynym wyjątkiem jest cennik, do którego przypięty jest feed
produktowy: zostaje jako pusty, wygasły cennik (krok 3). Przeczytaj poniższe kroki, zanim
uruchomisz polecenie. Pozostałe zmiany wydania następują w każdej części tej sekcji po zmianie cen:
minimalne ilości zamówienia i produkty podlegające zwrotowi, grupy pozycji w zapytaniach ofertowych
i zamówieniach, filtry list według pól niestandardowych i przenoszenie wartości pól
niestandardowych między rekordami, ceny jednostkowe z czterema miejscami po przecinku od cennika
po fakturę, edycja szans sprzedaży w miejscu, teksty produktów dla kanału
sprzedaży w storefroncie, akcja operatora przygotowująca koszyk klienta z oferty oraz nowy moduł
admin_feedback. Sekcja mówi, co zrobić i w jakiej kolejności, a potem — co działa inaczej dla
operatorów,
klientów API i integratorów,
autorów modułów i modułów nakładkowych oraz właściciela
istniejącego storefrontu. Sam model opisuje strona
Cenniki, a pełną listę tego, co usunięto, wraz z zamiennikami —
uwaga migracyjna
w przewodniku dla deweloperów.
Co zrobić i w jakiej kolejności
Przed aktualizacją:
- Zapisz każdy cennik inny niż Default: jego ceny, kogo wyceniał i w których kanałach.
Migration20261022T111815PriceListsPriceBooksConversionusuwa każdy cennik z regułą stosowania — kontrakty grup klientów i organizacji, promocje, cenniki katalogowe — razem z listą produktów, przedziałami, regułą i pozycjami macierzy cen, a potem usuwa stare tabele. Cennik, do którego przypięty jest feed produktowy, zostaje zamiast tego wycofany z użycia (krok 3), ale jest pusty, więc jego ceny i tak przepadają. To, co wyceniały, trzeba po aktualizacji wprowadzić ponownie jako cenniki; kopia bazy danych z Zanim zaczniesz jest jedyną kopią tych wierszy. - Zapisz cenę każdego produktu, który ma Default unit price, ale nie ma wiersza w cenniku
Default.
Migration20261022T111811CatalogPriceFieldsRemovedusuwadefaultPriceipricez wartości atrybutów każdego produktu oraz kolumnęproduct_variants.price_override; żadne z nich nie jest przenoszone. Produkt wyceniany tylko przez Default unit price po aktualizacji nie ma ceny — koszyk odrzuca go z kodem422 CART_PRODUCT_UNPRICED— dopóki nie wpisze się dla niego ceny bazowej. Price override wariantu nigdy nie był naliczany, więc jego usunięcie nie zmienia żadnej ceny. - Feed produktowy przypięty do cennika innego niż Default: nic nie trzeba robić. Feed
produktowy przypięty do cennika, który aktualizacja usuwa, pozostaje przypięty do wycofanej kopii
tego cennika: pustego, wygasłego cennika dla wszystkich, bez kanału sprzedaży i z tym samym id, o
nazwie
<name> (removed by the 0.108.0 upgrade)(dawna nazwa jest skracana tak, by całość mieściła się w 200 znakach). Strona feedu ostrzega, że jego cennik nie jest aktywny, a następne generowanie feedu wycofuje opublikowany plik, więc link odpowiada tak, jakby feed nigdy nie został wygenerowany — dopóki nie przypniesz feedu do cennika, który obowiązuje, albo go nie odepniesz (krok 9). Wycofany cennik możesz usunąć, gdy przestanie być potrzebny; feed nadal do niego przypięty ostrzega wtedy, że jego cennik już nie istnieje. - Jeśli Twoja integracja zapisuje ceny przez
priceListAdminPort— zwykle import cen konektora PIM — przygotuj jej nową wersję: port został usunięty, a zastępuje gopriceBookWritePort. Konektor zbudowany dla starego portu nie może w tym wydaniu zapisywać cen. Odpowiedniki metoda po metodzie są w uwadze migracyjnej przewodnika dla deweloperów.
Sama aktualizacja:
-
Uruchom
pnpm run upgrade 0.108.0.setupwykonuje migracje moduł po module, w kolejności zależności modułów, a migracje jednego modułu — w kolejności ich znaczników czasu; dlatego każda migracjacatalog, w tym usunięcie pól cen, wykonuje się przed konwersją. Według modułów:Migracja Moduł Co robi Migration20261016T082008CatalogOrderRulescatalogproducts.min_order_quantity,products.returnable,product_packaging_units.min_order_unitsMigration20261022T111811CatalogPriceFieldsRemovedcatalogUsuwa product_variants.price_override,defaultPriceiprice— zob. krok 2Migration20261022T182523CatalogAdminNameKeyscatalogZapisane, zindeksowane klucze nazwy i wyszukiwania w panelu na productsoraz rozszerzeniepg_trgmMigration20261019T103522CustomFieldsDefinitionListFiltercustom_fieldscustom_field_definitions.list_filter,falsedla każdego istniejącego polaMigration20261020T110920CustomFieldsDefinitionCarryTocustom_fieldscustom_field_definitions.carry_to, puste dla każdego istniejącego polaMigration20261016T082022OrdersOrderItemReturnableAtPurchaseordersorder_items.returnable_at_purchase, może byćnullMigration20261020T172007OrdersOrderItemGroupsordersTabela order_item_groups,order_items.group_idipositionMigration20261021T164807OrdersOrderCommentSystemNoteordersorder_comments.system_note, może byćnullMigration20261021T165247OrdersOrderItemsPriceSourceordersorder_items.price_source, może byćnullMigration20261024T145147OrdersVatMethodordersorders.vat_method, może byćnull;per_ratedla zamówienia złożonego w tym wydaniu, bez uzupełniania istniejącychMigration20261025T031732OrdersUnitPriceFourPlacesordersPoszerza order_items.unit_pricedonumeric(14,4)Migration20261018T122118QuoteRequestsItemPackagingUnitIdquote_requestsquote_request_items.packaging_unit_id, może byćnullMigration20261020T171341QuoteRequestsQuoteRequestItemGroupsquote_requestsTabela quote_request_item_groups,quote_request_items.group_idipositionMigration20261021T164808QuoteRequestsQuoteRequestEventCarrySkippedquote_requestsDopuszcza typ zdarzenia custom-field-carry-skippedMigration20261025T031736QuoteRequestsUnitPriceFourPlacesquote_requestsPoszerza quote_request_items.desired_unit_price,agreed_unit_priceireference_unit_pricedonumeric(14,4)Migration20261020T131018PwaPreferencesOrganizationAttributionCheckpwaOgraniczenie CHECKnapush_notification_preferencesMigration20261021T165244CartsCartItemsPriceSourcecartscart_items.price_source, może byćnullMigration20261025T031727CartsUnitPriceFourPlacescartsPoszerza cart_items.unit_price,recomputed_unit_priceiagreed_unit_pricedonumeric(14,4)Migration20261024T145631InvoicesVatSummaryinvoicesinvoices.vat_summary, może byćnull— podsumowanie według stawek, z którym wystawiono dokument; bez uzupełniania istniejącychMigration20261024T174052ReturnsCaseItemDiscountreturnsreturn_case_items.default_discount_amount,0dla każdej istniejącej pozycjiMigration20261023T195611ProductFeedsRunWithdrawalReasonproduct_feedsproduct_feed_runs.withdrawal_reason, może byćnull— dla generowania, które wycofało opublikowany plikMigration20261023T234605ProductFeedsPriceListFkDroppedproduct_feedsUsuwa product_feeds_price_list_fk, więc wycofany cennik można później usunąć; wykonuje się po konwersjiMigration20261021T165239PriceListsPriceBooksExpandprice_listsNagłówek cennika, zbiory odbiorców i kanałów, rozszerzone wiersze, wersja kolejności 1, tabele powierzchni cen Migration20261021T232310PriceListsPriceBooksVersionprice_listsprice_lists.versioniever_activatedMigration20261022T012134PriceListsPriceSurfaceGenerationsprice_listsGeneracje powierzchni cen Migration20261022T111815PriceListsPriceBooksConversionprice_listsKonwersja — zob. krok 1 i niżej Migration20261022T182527PriceListsProductLinesprice_listsTabela price_list_product_lines, wypełniona na podstawie zapisanych wierszyMigration20261023T152351PriceListsPriceSurfaceTargetedMarksprice_listsTabela price_profile_stale_productsiprice_profiles.revisiondo odświeżania powierzchni cen tylko tam, gdzie to potrzebneKonwersja wykonuje się w jednej transakcji: tworzy jeden cennik cen bazowych na każdą walutę użytą w przedziale cennika Default oraz na domyślną walutę platformy; zamienia każdy przedział cennika Default w stały wiersz cennika cen bazowych swojej waluty; usuwa każdy inny cennik poza tym, do którego odwołuje się jeszcze inna tabela — przypięty feed — który wycofuje z użycia jako pusty, wygasły cennik z tym samym id (krok 3); usuwa stare tabele i kolumny.
Migration20261022T182523CatalogAdminNameKeysdodaje doproductscztery kolumny generowane, a każda z nich przepisuje tabelę pod blokadą wyłączną — w pomiarze platformy 3,5–7 s na kolumnę przy 50 000 produktów — co jest jeszcze jednym powodem, by API pozostało zatrzymane.
Po aktualizacji, zanim instancja znów przyjmie ruch:
- Upewnij się, że działa konsument kolejek. Listy sortowane i filtrowane według ceny są
porządkowane według cen, które z wyprzedzeniem wylicza worker
price_lists.surface; działa on w procesie, który obsługuje kolejki (BACKEND_ROLEnieustawione,allalboworker) i ma połączenie Redis dla kolejek — Konsumenci kolejek. Aktualizacja niczego nie buduje: pierwsze żądanie sortowane według ceny w każdym kanale sprzedaży i walucie rejestruje to, czego potrzebuje, i zleca to workerowi, a do czasu, aż worker to zbuduje, dostaje domyślny porządek katalogu — z poprawnymi cenami na kartach produktów. Bez konsumenta sortowanie według ceny nigdy nie porządkuje według ceny. - Sprawdź ceny bazowe. Otwórz zakładkę Pricing kilku produktów: ceny pochodzące z cennika Default są tam, dla każdej waluty. Wpisz cenę bazową każdego produktu z kroku 2.
- Utwórz ponownie cenniki, które zastępują cenniki z kroku 1, w Cennikach (
/price-listsalbo ⌘K / CTRL+K → Cenniki). Cennik ma jedną walutę, zbiór kanałów sprzedaży, odbiorców — wszystkich, wybrane grupy klientów albo wybrane organizacje — i linie; niczego nie wycenia, dopóki nie zostanie aktywowany. Wyjaśnienie ceny (/price-lists/explain) pokazuje dla kupującego i produktu, która linia zdecydowała o cenie. - Przypnij ponownie feedy z kroku 3 albo je odepnij przez API feedów produktowych
(
PATCH /api/v1/admin/product-feeds/:idzpriceListId— id cennika albonull); w panelu nie ma jeszcze do tego kontrolki (zgłoszenie #414). Przypnij każdy do cennika, który obowiązuje kogokolwiek w kanale feedu: Aktywny albo Zaplanowany w swoich datach, przypisany do kanału feedu i dla wszystkich. Feed przypięty do każdego innego cennika nie publikuje z niego żadnych cen — strona feedu mówi dlaczego — a jego następne generowanie kończy się stanem Wycofany i wycofuje opublikowany plik, więc link odpowiada tak jak dla feedu nigdy niewygenerowanego, dopóki cennik znów nie zacznie obowiązywać. Wycofanie nie jest błędem: nie wywołuje powiadomienia i niczego nie dostarcza. - Oznacz produkty, które kupujący mogą zwracać.
Migration20261016T082008CatalogOrderRulesdodaje każdemu produktowi flagę Podlega zwrotowi, wyłączoną: pozycja zamówienia złożonego po aktualizacji podlega zwrotowi tylko wtedy, gdy jej produkt podlegał zwrotowi w chwili złożenia zamówienia, a zwrot (kind: 'return') obejmujący każdą inną pozycję jest odrzucany z kodem422 RETURN_LINE_NOT_RETURNABLE. Zamówienia złożone przed aktualizacją się nie zmieniają — ich pozycje nie mają zapisanej wartości i nadal podlegają zwrotowi — a reklamacje nigdy nie są ograniczane. Zaznacz produkty na liście produktów i użyj Edycja masowa → Podlega zwrotowi → Tak — podlega zwrotowi; flaga jednego produktu jest na karcie Zasady zamawiania w jego zakładce Inventory.
W dowolnym momencie:
- Nadaj
catalog:readkażdej roli, która maprice_lists:write. Dodawanie linii produktów do cennika korzysta z przeglądarki produktów katalogu, którą chronicatalog:read; edytor ról w Admin roles (/admin-roles) pokazuje brak dla roli, która go nie ma, z dodaniem jednym kliknięciem. Oba kody uprawnień cenników się nie zmieniły; ich etykiety brzmią teraz View price books and display modes oraz Edit price books, base prices and display modes. - W instancji z danymi sklepu demonstracyjnego uruchom
pnpm run cli demo reset, a potempnpm run cli demo seed: konwersja usunęła dawne cenniki wersji demonstracyjnej, a ponowne wczytanie przywraca jej ceny bazowe i cenniki przykładów ze strony Cenniki. - Zdecyduj, czy dodać
admin_feedback. Wydanie wprowadza nowy pakiet@endora-commerce/mod-admin-feedback, który pozwala administratorowi wysłać opinię twórcom Endora Commerce przez link Wyślij opinię na pasku bocznym panelu. Żaden moduł od niego nie zależy, więc aktualizacja go nie deklaruje; jak to zrobić, mówi sekcja Dodawanie modułu, który jest nowy w wydaniu. Po zadeklarowaniu jest włączony i wysyła dane do Endora (admin_feedback.relay_url, domyślniehttps://commerce.endora.software/api/platform-feedback/): każda wiadomość zawiera wpisany tekst, adres e-mail nadawcy i opcjonalny telefon, język i adres panelu, wydanie, na którym działa instancja, jej skonfigurowany publiczny adres API oraz imię i nazwisko i id administratora, który ją wysłał. Wyłącz moduł w Ustawienia → Feedback (admin_feedback.module_enabled) albo wyczyść adres przekaźnika, jeśli instancja nie może wysyłać danych do Endora. Instancja niczego nie zapisuje. - Oznacz pola niestandardowe, według których listy w panelu mają filtrować. Listy zamówień,
zapytań ofertowych i szans sprzedaży oferują filtr dla każdego pola niestandardowego zamówienia,
zapytania ofertowego albo szansy sprzedaży, które ma włączoną nową opcję Używaj jako filtra
listy, a każde istniejące pole ma ją wyłączoną. Włącz ją w edytorze pola na
/custom-fields, który teraz pozwala edytować istniejące pola. - Podnieś
viteinodemailerwe własnych manifestach. Opcjonalny zakres peer powłoki panelu dlaviteto teraz^7.3.7; instancja, której człon panelu wciąż rozwiązuje wcześniejszyvite7, dostaje ostrzeżenie unmet peer (Po zakończeniu) — podnieśvitewadmin/package.jsondo^7.3.7.mod-emailimod-newsletternadal przyjmują każdynodemailer10, więc instancja zachowuje ten, który rozwiązała; platforma buduje się teraz z^10.1.4(10.2.1 w jej pliku blokady), który zaostrza parsowanie adresów, usuwa dane uwierzytelniające przy przekierowaniu do innego źródła i ogranicza pracę, jaką może wywołać jedna wiadomość. Podnieśnodemailertam, gdzie instancja go deklaruje, do^10.1.4, żeby mieć te poprawki, a potem uruchompnpm install.
Dla operatorów
Ceny
- Cenniki działają w nowym modelu. Pozycja paska bocznego Cenniki otwiera listę cenników; pole
Default unit price w edytorze produktu, kolumna Price override w tabeli wariantów i strefa
Linked price lists produktu zniknęły, a ceny bazowe produktu są w jego zakładce Pricing.
Ustawienia cenowe (⌘K / CTRL+K) zawierają tryby prezentacji, przełącznik pokazywania ceny
katalogowej obok ceny kupującego (
pricing.show_list_price, domyślnie wyłączony) i kolejność cen, tylko do odczytu. - Jak ustalana jest cena — cena bazowa, cena katalogowa dla wszystkich w kanale, cena kontraktowa kupującego i cena promocyjna naliczana tylko wtedy, gdy jest niższa — opisuje strona Cenniki, z przykładami.
- Zmiana ceny pojawia się na liście sortowanej według ceny w ciągu około pół sekundy w pomiarach platformy; zmiana, która sięga całego katalogu — linia dla wszystkich produktów albo tylko dla wartości atrybutu, ponad 10 000 produktów naraz, zmiana kanału sprzedaży — trwa poniżej około półtorej minuty przy mniej więcej 200 grupach kupujących z własnymi cennikami (zmierzono 85 s przy 202, na 50 000 produktów, wobec około 4,5 minuty przed zgłoszeniem #416). Ceny na kartach produktów nigdy się nie opóźniają.
- Import produktów odrzuca plik z kolumną ceny —
default_price,defaultPrice,price,price_overridealbopriceOverride— z kodem400 PRICE_SET_IN_PRICE_BOOKSi niczego nie importuje. Ceny importuje się przez import CSV cenników albo konektorem PIM przezpriceBookWritePort. - Zalogowany klient jest wyceniany według własnej grupy klientów, gdy jego konto ma grupę inną niż jego organizacja — na listach, na stronie produktu, w wyszukiwaniu i podpowiedziach, w porównaniach, w koszyku i przy składaniu zamówienia. Koszyk pokazuje też cenę, jaką naliczy złożenie zamówienia: pozycja z zaakceptowanej oferty pokazuje cenę uzgodnioną przy uzgodnionej ilości, a pozycja, którą składanie zamówienia by odrzuciło, jest oznaczona jako niedostępna.
- Ponowne zamówienie pomija pozycje, których nic już nie wycenia dla kupującego albo które są sprzedawane tylko na zapytanie, i informuje o nich; wcześniej taka pozycja trafiała do koszyka w cenie ze starego zamówienia, a koszyk był potem odrzucany przy składaniu zamówienia.
Kwoty, VAT i faktury
- VAT jest liczony w jeden sposób na każdym dokumencie: dla każdej stawki od sumy kwot netto w tej stawce, z zaokrągleniem „połówkowym w górę” jeden raz, a kwota brutto to netto plus ten VAT (art. 106e ust. 1 pkt 13–14 ustawy o VAT). Te same kwoty pokazują checkout, zamówienie, otwarta przez nie płatność i proforma, faktura, korekta i zwrot. Koszyk z pozycjami w dwóch stawkach VAT może kosztować o grosz więcej lub mniej niż przed aktualizacją: 29,50 w stawce 23 % z 4,30 w stawce 5 % dawało 40,80 brutto, a daje 40,81 — tyle samo, ile mówi jego faktura. Kwota brutto pozycji w zamówieniu, jego potwierdzeniu, na fakturze i na ekranach zamówień to własne zaokrąglenie pozycji — 29,50 w stawce 23 % pokazuje 36,29 — podawane informacyjnie; VAT i sumy to kwoty według stawek, więc kwoty brutto pozycji dokumentu mogą sumować się do grosza więcej lub mniej niż jego suma.
- Faktury wystawione przed aktualizacją dla zamówień czynnych podatników VAT zawyżały kwotę netto, VAT i brutto. Przy wystawianiu kwota brutto każdej pozycji zamówienia była odczytywana jako jej kwota netto i opodatkowana ponownie: pozycja 100,00 netto w stawce 23 % trafiała na fakturę jako 123,00 netto i 151,29 brutto. Aktualizacja zmienia sposób wystawiania faktur od teraz. Nie poprawia faktur już wystawionych, a platforma nie wystawia do nich korekt; zachowują kwoty, z którymi je wystawiono, a ich podsumowanie według stawek jest pokazywane tak, jak je wystawiono.
- Zamówienie złożone przed aktualizacją zachowuje to, co naliczyło. Jego zwroty oddają to, co zapisało i co zostało zapłacone. Faktura wystawiona do niego po aktualizacji liczy VAT według stawek i może być o grosz wyższa od zapłaconej kwoty — 40,81 dla zamówienia, które naliczyło 40,80 — a jako zapłacone zapisuje tylko to, co zapłacono, więc grosz pokazuje jako należny (Do zapłaty: 0,01), a nie jako zapłacony.
- Zwroty domyślnie oddają to, co zapłacono, łącznie ze wszystkich zwrotów zamówienia. Każdy zwrot oddaje wartość wszystkiego, co dotąd zwrócono, pomniejszoną o to, co oddały wcześniejsze zwroty, więc żaden ciąg zwrotów nie odda więcej, niż naliczyło zamówienie, a zwrot całości oddaje dokładnie tyle: dwa zwroty po jednej z dwóch sztuk po 29,50 w stawce 23 % oddają 36,29, a potem 36,28 (zapłacono 72,57), gdzie każdy oddawał 36,29. Reklamacja pozycji, której nie można zwrócić, liczy się do tej samej sumy, a rabat na całe zamówienie jest odejmowany od tego, co oddaje zwrot towaru — zamówienie naliczyło towar pomniejszony o rabat, więc zwrot całości oddaje tyle, nigdy pełnej ceny towaru. Korekta uznaje tak samo: VAT towaru tak, jak naliczyła go faktura, a rabat na osobnej pozycji, więc wszystkie korekty jednej faktury razem uznają dokładnie to, co naliczyła. Dla zamówienia złożonego przed aktualizacją zwrot oddaje to, co naliczyło zamówienie, a korekta uznaje dokładnie tyle.
- Rozliczenie sprawdza korektę, zanim przesunie jakiekolwiek pieniądze. Odmowa — ta sama pozycja
zgłoszenia podana dwa razy, pozycja faktury, której korekta nie może dopasować, wyłączony moduł
invoices— zostawia zgłoszenie nierozliczone, bez zwrotu i bez uznania. Rozliczenie zwraca wszystkie pozycje zgłoszenia: pozycja pominięta w żądaniu jest zwracana i uznawana w zapisanej zatwierdzonej kwocie, więc zwrot i korekta zawsze się zgadzają. Dwa zwroty jednego zamówienia są rozliczane jeden po drugim. - Zamówienie zwrócone w całości przed wystawieniem faktury nie jest fakturowane. Wystawienie jego faktury VAT jest odrzucane — Płatności za to zamówienie zostały w całości zwrócone przed wystawieniem faktury, więc nie ma sprzedaży do zafakturowania. — i nie pobiera numeru, a automatyczne wystawianie po zmianie statusu zamówienia je pomija. Zamówienie zwrócone w części jest fakturowane, a jako zapłacone wykazuje tylko to, co zostało zatrzymane.
- Kwota z więcej niż dwoma miejscami po przecinku jest odrzucana, a nie zaokrąglana — koszt dostawy, dopłata do metody płatności, zatwierdzona kwota zwrotu, koszt zwrotu, limit kredytowy, wartość szansy sprzedaży oraz kwota stała, próg i minimalna wartość koszyka w promocji. Ekran odpowiada komunikatem Kwota może mieć najwyżej dwa miejsca po przecinku. Cena jednostkowa nie jest kwotą: zob. następną sekcję.
Ceny jednostkowe z czterema miejscami po przecinku
- Ceny jednostkowe nie są już zaokrąglane do groszy w koszyku. Cennik zawsze przechowywał cztery miejsca po przecinku, ale do tego wydania koszyk zaokrąglał cenę do dwóch, przyjmując ją: wkręt w cenie 0,0125 zł był liczony po 0,01 zł, więc 10 000 sztuk kosztowało 100,00 zł zamiast 125,00 zł, a wkręt po 0,0150 zł był liczony po 0,02 zł — o jedną trzecią za drogo. Cena jednostkowa zachowuje teraz cztery miejsca, bez zaokrąglania, w pozycji koszyka, pozycji zamówienia, pozycjach zapytania ofertowego i pozycji faktury.
- Zaokrąglana jest wartość pozycji. Wartość netto pozycji to cena jednostkowa × ilość, zaokrąglona raz, połówkowo w górę, do grosza: 0,0125 × 10 000 = 125,00, a 0,0150 × 3 = 0,045, czyli 0,05. VAT pozostaje taki, jak opisano wyżej — dla każdej stawki od sumy wartości pozycji. Zwroty i korekty liczą się, jak dotąd, z tego, co zamówienie zapisało i pobrało.
- Kolumny zostały poszerzone:
cart_items.unit_price,recomputed_unit_priceiagreed_unit_price,order_items.unit_priceorazquote_request_items.desired_unit_price,agreed_unit_priceireference_unit_pricemają teraz typnumeric(14,4), jak cenniki iinvoice_lines.unit_net_price. Poszerzenie jest bezstratne: żadna zapisana wartość się nie zmienia, a zamówienia złożone przed aktualizacją zachowują dwumiejscowe ceny, po których je złożono. - Cena jednostkowa pokazuje swoje znaczące miejsca, do czterech: 0,0125 zł, 1,505 zł — oraz dwa miejsca, gdy trzecie i czwarte są zerami (1,50 zł). Wartości pozycji i sumy zawsze mają dwa miejsca. Dotyczy to panelu, storefrontu, kolumny Cena netto na fakturze PDF, oferty PDF i e-maili. Feed produktowy nadal publikuje dwa miejsca.
- Uzgodnioną lub proponowaną cenę jednostkową w zapytaniu ofertowym można wpisać z czterema miejscami po przecinku; piąte jest odrzucane komunikatem Cena jednostkowa może mieć najwyżej cztery miejsca po przecinku.
- Pozycja koszyka dodana przed aktualizacją ma dwumiejscową cenę. Jeśli cena produktu ma trzecie lub czwarte miejsce po przecinku, nie jest to już cena bieżąca i koszyk przelicza pozycję przed złożeniem zamówienia — np. po 0,0125 zamiast 0,01.
Minimalne ilości zamówienia i produkty podlegające zwrotowi
- Produkt może mieć minimalną ilość zamówienia (w sztukach, dla produktów prostych i konfigurowalnych), a jednostka opakowania — minimalną liczbę jednostek: karta Zasady zamawiania w zakładce Inventory produktu i kolumna Minimum (w jednostkach) jego jednostek opakowania. Istniejące produkty nie mają minimum, więc nic się nie zmienia, dopóki go nie ustawisz. Minimum jest oceniane na sumie produktu ze wszystkich pozycji i wariantów koszyka.
- Gdzie jest egzekwowane. Koszyk odrzuca dodanie albo zmianę, po której produkt byłby poniżej minimum, a przesłanie koszyka do akceptacji, finalizacja zamówienia, zakup jednym kliknięciem, ponowne zamówienie, Utwórz zamówienie w panelu i zewnętrzne API zamówień odrzucają koszyk poniżej minimum. Pozycja z zaakceptowanego zapytania ofertowego jest zwolniona przy uzgodnionej ilości, dopóki oferta jest ważna. Zapytanie ofertowe kupującego poniżej minimum jest odrzucane; to, które tworzy albo modyfikuje operator, zostaje zapisane, a pozycja dostaje znaczek Poniżej minimum. Import szybkiego zamówienia przez operatora oznacza takie wiersze jako ostrzeżenia, a import kupującego je odrzuca. Zakup jednym kliknięciem zamawia minimalną ilość produktu zamiast 1.
- Zwroty uwzględniają flagę Podlega zwrotowi (krok 10): zwrot pozycji, która nie podlegała
zwrotowi w chwili zakupu, jest odrzucany; reklamacja takiej pozycji jest przyjmowana z
zatwierdzoną kwotą zwrotu
0wpisaną domyślnie, a sprawa oznacza ją jako Bez prawa zwrotu przy zakupie. - Limity. Utwórz zamówienie w panelu, zewnętrzne API zamówień i budowanie szybkiego
zamówienia przyjmują najwyżej 200 pozycji;
quick_order.import_max_rowsjest ograniczane do 1–10 000 niezależnie od zapisanej wartości.
Grupy pozycji w zapytaniach ofertowych i zamówieniach
- Pozycje zapytania ofertowego można układać w nazwane, uporządkowane grupy w jego zakładce Modify i na ekranie tworzenia, z podsumowaniem każdej grupy; Podziel pozycję przenosi część ilości pozycji na nową pozycję, a jeden produkt może być na kilku pozycjach. Zamówienie złożone z zaakceptowanej oferty przejmuje jej grupy, a Utwórz zamówienie w panelu ma ten sam edytor. Grup zamówienia nie można zmienić po jego złożeniu. PDF oferty drukuje grupy jako sekcje, podobnie jak każdy szablon dokumentu, którego tabela pozycji nie ma wybranego grupowania, także szablony zapisane przed aktualizacją — jej domyślnym ustawieniem są teraz Grupy z dokumentu. Dokument bez grup drukuje się jak wcześniej; wybierz Bez grupowania w edytorze szablonu, aby tabela pozostała płaska niezależnie od dokumentu.
Pola niestandardowe
- Filtry list (krok 14) na listach zamówień, zapytań ofertowych i szans sprzedaży: zapisywane w adresie, w zapisanych widokach i — na liście zamówień — w eksporcie CSV.
- Istniejące definicje można edytować na
/custom-fields: etykiety, Wymagane, Używaj jako filtra listy i opcje pola wyboru; klucz i typ wartości pozostają stałe. - Wartości mogą być przenoszone między rekordami. Opcje Przenoś wartość do: … definicji przenoszą jej wartości z zapytania ofertowego do złożonego z niego zamówienia i do jego szansy sprzedaży, z zamówienia do jego szansy sprzedaży oraz z szansy sprzedaży do zapytania ofertowego albo zamówienia, które administrator z niej tworzy — tylko do pustych pól. Wartość, której nie da się przenieść, jest wymieniona w wewnętrznej notatce systemowej zamówienia albo w historii zapytania ofertowego. Żadna istniejąca definicja niczego nie przenosi.
- Pola niestandardowe zapytań ofertowych podlegają modułowi
quote_requests: gdy jest wyłączony, nie są wyświetlane ani zapisywalne na/custom-fields, a wracają bez zmian razem z nim. - Historia zmian szansy sprzedaży pokazuje wartości pól niestandardowych tylko roli z
uprawnieniem
custom_fields:read.
Szanse sprzedaży
- Ekran szansy sprzedaży jest edytowany w miejscu — tytuł, opis, przewidywana data zamknięcia, osoba kontaktowa, kanał sprzedaży i wartości typowane, każde tam, gdzie jest pokazywane — a opisy, notatki, wiadomości i opisy zdarzeń pisze się w edytorze tekstu sformatowanego. Odwołanie do zamówienia pokazuje jego stan, sumę, datę i klienta.
Zapytania ofertowe
- Akcja Przygotuj koszyk klienta zatwierdzonego zapytania ofertowego umieszcza jego pozycje w koszyku klienta, do którego należy, po uzgodnionych cenach, zastępując zawartość tego koszyka; klient kończy zamówienie przy finalizacji. Wcześniejszy przycisk Place order wylogowywał administratora.
Teksty produktów w storefroncie
- Teksty produktu dla kanału sprzedaży trafiają do storefrontu. Nazwa, opis, pola SEO i wartości atrybutów zapisane dla kanału sprzedaży w edytorze zakresu produktu są tym, co pokazuje strona produktu w storefroncie w tym kanale, a zapisana tam nazwa — tym, co pokazują karty list, wyniki wyszukiwania i podpowiedzi.
Opinie z panelu
admin_feedback(krok 13) dodaje Wyślij opinię obok numeru wydania na pasku bocznym, dla każdego zalogowanego administratora, najwyżej pięć wiadomości na administratora w ciągu dziesięciu minut.
Dla klientów API i integratorów
Ceny
- Dawne trasy cen w API administracyjnym zostały usunięte:
/api/v1/admin/price-lists-engine/…,/api/v1/admin/pricing/rule-targets/…,/api/v1/admin/products/:id/price-listsi/api/v1/admin/products/:id/resolved-price. Zastępuje je/api/v1/admin/price-books/…:GET ?productId=dla cenników produktu iPOST /api/v1/admin/price-books/resolvedla wyliczonej ceny z wyjaśnieniem. - Zapis katalogu, który podaje cenę, jest odrzucany:
POST/PATCH /api/v1/admin/catalog/products[/:id]zattributeValues.defaultPricealboattributeValues.priceoraz zapis wariantu zpriceOverridezwracają400 PRICE_SET_IN_PRICE_BOOKSi niczego nie zapisują. Ceny bazowe zapisuje się przezPUT /api/v1/admin/price-books/base-prices/:productId. GET /api/v1/storefront/products/:id/resolved-priceuczciwie opisuje produkt bez ceny:baseListId,basePriceiquantityBracketmają wszystkie wartośćnull, podczas gdy wcześniejbaseListIdbył pustym tekstem. Kwoty są tekstami dziesiętnymi ({ "amount": "199.0000", "currency": "PLN" }), tak jak trasa zawsze je wysyłała. Odpowiedź zawiera teżsource(który krok wycenił produkt),compareAtoraz — gdy sklep ją pokazuje —listPrice.- Kody błędów macierzy cen zniknęły (
PRICE_MATRIX_ENTRY_NOT_FOUND,PRICE_MATRIX_ENTRY_OVERLAP,PRICE_MATRIX_ATTRIBUTE_NOT_ELIGIBLE,PRICE_MATRIX_ENTRY_LIMIT); zastępują je kody cenników,PRICE_BOOK_*iPRECEDENCE_CHANGE_UNAVAILABLE. - Feed produktowy przypięty do cennika publikuje tylko wtedy, gdy cennik jest Aktywny albo
Zaplanowany w swoich datach, w kanale feedu i dla wszystkich. Generowanie feedu, którego cennik
nie obowiązuje, kończy się nowym stanem
withdrawnzwithdrawalReason(powód;nullw każdym innym generowaniu), czyści opublikowany plik, a publiczny URL odpowiada404; klient, który obsługuje wyczerpująco każdy stan generowania, ma jeden przypadek więcej. Kod błęduprice_list_unavailable, którego nic nie zgłaszało, został usunięty. Feed w API administracyjnym (GET /api/v1/admin/product-feeds,GET …/:id) zawierapinnedPriceBookWarning: powód, dla którego cennik nie jest publikowany, albonull. POST /api/v1/orders/:id/reorderzgłasza pozycję pominiętą, bo nic jej nie wycenia, zreason: 'no_price'wunavailableItems.
Kwoty i VAT
- Wpisana kwota pieniężna z więcej niż dwoma miejscami po przecinku zwraca
400 AMOUNT_PRECISION_EXCEEDED(nowy kod) i nie jest zapisywana; wcześniej była zaokrąglana. Dotyczy to pól:costmetody dostawy,additionalPricemetody płatności,approvedRefundAmountrozliczenia,returnCostmetody dostawy zwrotu,amountzwrotu zamówienia w panelu,grantedAmountlimitu kredytowego,manualValueszansy sprzedaży,amount,spendStep,minCartSubtotalpromocji ivaluepromocjiamount_off, a takżenetTotal,taxTotal,grossTotalipaidTotaldokumentu sprzedaży z integracji ERP.detailswskazuje każde pole z problememAMOUNT_PRECISION_EXCEEDED, a komunikat jest w języku czytelnika. Żądanie, które nie spełnia także innych warunków, zwraca400 VALIDATION_FAILEDz tym samym problemem wśród szczegółów. Końcowe zera nie są znaczące ("123.4500"to 123,45); ciąg, który w ogóle nie jest liczbą dziesiętną, zwraca400 VALIDATION_FAILEDz problemem formatu. - Cena jednostkowa ma do czterech miejsc po przecinku.
desiredUnitPriceiagreedUnitPricepozycji zapytania ofertowego (POST /api/v1/quote-requests, trasy tworzenia iPATCHw panelu) przyjmują cztery miejsca; piąte zwraca400 UNIT_PRICE_PRECISION_EXCEEDED(nowy kod, Cena jednostkowa może mieć najwyżej cztery miejsca po przecinku.), adetailswskazuje pole. Żądanie odrzucone jednocześnie za kwotę i za cenę jednostkową zwraca400 VALIDATION_FAILED. Odpowiedzi niosą czteromiejscową cenę jednostkową: liczby (unitPricepozycji koszyka i zamówienia,agreedUnitPrice,unitNetPricepozycji faktury) jako wartość —0.0125— a napisy dziesiętne z czterema miejscami —unitPricepodglądu zamówienia w panelu to"0.0125"lub"29.4950", gdzie wcześniej było zaokrąglone do"29.50"; tak samopreviousUnitPriceicurrentUnitPricewdetails.linesodpowiedzi409 CART_PRICE_CHANGED. Wartość pozycji tounitPrice × quantity, zaokrąglona raz, połówkowo w górę, do dwóch miejsc: licz ją w ten sposób, a nie z ceny jednostkowej zaokrąglonej do dwóch miejsc. Zamówienie złożone przed aktualizacją zachowuje dwumiejscowe ceny. - VAT jest liczony dla każdej stawki od sumy kwot netto w tej stawce, z jednym zaokrągleniem
„połówkowym w górę”:
taxTotalitotalzamówienia, podglądy w checkoucie i w panelu,vatSummary,taxTotaligrossTotalfaktury oraz kwoty korekty.lineTotalpozycji zamówienia igrossValuepozycji faktury to własne zaokrąglenie pozycji,round(net × (1 + rate)), i mogą sumować się do grosza więcej lub mniej niż suma. Kształty się nie zmieniają. Zamówienie w dwóch stawkach może różnić się o grosz od tego samego koszyka sprzed aktualizacji. paidTotalfaktury to to, co zapłacono za zamówienie — każda przechwycona płatność pomniejszona o to, co z niej zwrócono, albo zapisana suma zamówienia, gdy jest ono oznaczone jako opłacone bez przechwyconej płatności — nigdy własna kwota brutto faktury, więcamountDuemoże wynosić0.01dla zamówienia sprzed aktualizacji. Zamówienie zwrócone częściowo przed wystawieniem faktury dostaje fakturę, z zapłaconą kwotą równą temu, co zatrzymano.- Zamówienie zwrócone w całości przed wystawieniem faktury nie jest fakturowane. Gdy zwroty jego
płatności są równe temu, co pobrano, i pokrywają całą kwotę zamówienia, nie ma już sprzedaży do
udokumentowania: wystawienie faktury VAT zwraca
422 INVOICE_ORDER_REFUNDED(Płatności za to zamówienie zostały w całości zwrócone przed wystawieniem faktury, więc nie ma sprzedaży do zafakturowania. Niczego nie wystawiono.) i nie pobiera numeru, a automatyczne wystawianie po zmianie statusu zamówienia pomija je i zapisuje w logu[invoices] the order was not auto-invoiced: its payments were refunded in full. - Rozliczenie, które podaje tę samą pozycję zgłoszenia dwa razy, zwraca
400 VALIDATION_FAILED(problemduplicate_line) i niczego nie przesuwa; rozliczenie, którego korekta zostałaby odrzucona, jest odrzucane przed zwrotem. totalRefundAmountrozliczenia obejmuje wszystkie pozycje zgłoszenia, nie tylko te zlines: pozycja pominięta liczy się w zapisanej kwocieapprovedRefundAmount, a korekta uznaje te same pozycje. Rozliczenia jednego zamówienia wykonują się jedno po drugim; rozliczenie, które czeka na inne dłużej niż 15 sekund, zwraca409 VALIDATION_FAILED(details.code: settlement_in_progress), niczego nie przesuwa i można je ponowić.- Korekta zamówienia z rabatem uznaje towar według wartości z faktury, a rabat na ujemnej pozycji 0 %; anulowana korekta nie liczy się już do kolejnych.
- Korekta odrzuca
totalwyższy od sumy jej pozycji odpowiedzią400 VALIDATION_FAILED(details.code: correction_total_exceeds_lines), a korekty jednej faktury uznają narastająco: razem nigdy więcej, niż naliczyła. - Faktury wystawione przed aktualizacją się nie zmieniają. Faktura wystawiona dla zamówienia
czynnego podatnika VAT zawyża
netTotal,taxTotaligrossTotal(netto każdej pozycji było kwotą brutto pozycji zamówienia); jej podsumowanie według stawek jest pokazywane tak, jak ją wystawiono.
Minimalne ilości zamówienia i produkty podlegające zwrotowi
- Nowe odmowy.
422 PRODUCT_BELOW_MINIMUM_QUANTITYi422 PACKAGING_UNIT_BELOW_MINIMUM_QUANTITY(details.lines[]) przy dodawaniu i zmianie pozycji koszyka,POST /api/v1/cart/submit-for-approval, składaniu zamówienia, w zewnętrznym API zamówień i przy zapisach zapytań ofertowych kupującego;422 PACKAGING_UNIT_MISMATCHdla pozycji zapytania ofertowego, której jednostka opakowania nie należy do produktu;422 RETURN_LINE_NOT_RETURNABLEzPOST /api/v1/returnszkind: 'return'. Nazwa produktu w takiej odmowie jest w języku, w którym udzielana jest odpowiedź — według kolejnościqwAccept-Language, a w przeciwnym razie w domyślnym języku kanału — podobnie jak nazwy pozycji wGET /api/v1/cart, które nie są już domyślnie po angielsku. - Limit 200 pozycji.
POST /api/v1/admin/orders,POST /api/v1/admin/orders/preview,POST /api/v1/external/ordersi budowanie szybkiego zamówienia odrzucają ponad 200 pozycji z kodem422 VALIDATION_FAILED,details: { code: 'too_many_items', maximum: 200 }; w zewnętrznym API jest to sprawdzane, zanim wymagany jest nagłówekIdempotency-Key. - Nowe pola.
minOrderQuantityw podsumowaniach produktu, wyszukiwania i porównania;minOrderUnitsw jednostkach opakowania;minOrderQuantityibelowMinimumw pozycjach koszyka;belowMinimumLines[]wPOST /api/v1/orders/preview-total;returnableinotReturnableReasonw pozycjachGET /api/v1/orders/:orderId/returnable, któregoeligible: falsez powodemno_returnable_linesnadal pozwala na reklamację. Zapis produktu w panelu przyjmujeminOrderQuantityireturnable. GET /api/v1/cartodpowiada5xx, gdy nie da się odczytać katalogu, zamiast koszyka bez oznaczeń minimum.
Grupy pozycji
- Każda odpowiedź z zamówieniem i zapytaniem ofertowym zawiera
itemGroupsorazgroupIdipositionkażdej pozycji, aitemssą w kolejności wyświetlania — wcześniej odpowiedzi nie miały określonej kolejności. PanelowePOST/PATCHzapytania ofertowego iPOST /api/v1/admin/ordersprzyjmujągroupsigroupKeypozycji.POST /api/v1/admin/orders/previewzwracataxRatekażdej wycenionej pozycji.
Pola niestandardowe
- Filtry list. Lista zamówień w panelu i jej eksport CSV, lista zapytań ofertowych i lista
szans sprzedaży przyjmują
customFieldFilters(JSON z kluczami pól niestandardowych); klucz, którego lista nie oferuje, jest odrzucany z kodem400 CUSTOM_FIELD_FILTER_UNAVAILABLE. Pola oferowane przez każdą listę są podGET /api/v1/admin/orders/custom-field-filters,GET /api/v1/admin/quote-requests/custom-field-filtersiGET /api/v1/admin/crm/opportunities/custom-field-filters. WGET /api/v1/admin/crm/opportunitiesbezcardValues=trueodwołaniecustom:wfieldFilterspodlega teraz opcji filtra listy, a nie karcie tablicy. - Definicje. Definicje mają
listFiltericarryTo; trzy odmowy 409, które dzieliły kodCUSTOM_FIELD_DEFINITION_INVALID, mają teraz własne kody (CUSTOM_FIELD_OPTION_IN_USE,CUSTOM_FIELD_VALUE_TYPE_LOCKED,CUSTOM_FIELD_STORED_VALUES_EXIST), aPATCHopcji zmienia tylko podane w nim pola. Włączenie przenoszenia do pola, które nie może przyjąć wartości, jest odrzucane z kodem422 CUSTOM_FIELD_CARRY_INCOMPATIBLE. - Zapisy przenoszenia.
GET /api/v1/admin/orders/:id/commentszwraca dla każdego komentarzasystemNote(nulldla zwykłego); odczyt komentarzy przez klienta nigdy nie zawiera notatki systemowej. Historia zapytania ofertowego w panelu zyskuje wpiscustom_field_carry_skipped. Historia szansy sprzedaży pomijacustomFieldValuesdla wywołującego bezcustom_fields:read.
Zapytania ofertowe
POST /api/v1/admin/quote-requests/:id/prepare-customer-basket(rfqs:handle) przygotowuje koszyk klienta, do którego należy zatwierdzone zapytanie; panel nie wywołuje już klienckiej trasyconvert-to-order.
Odczyty produktów w storefroncie
GET /api/v1/catalog/products/:idOrSlug,GET /api/v1/catalog/productsoraz wyniki i podpowiedzi wyszukiwania zwracają nadpisanie produktu dla kanału sprzedaży i języka żądania; produkt bez nadpisania odpowiada dokładnie tak jak wcześniej.
Opinie
POST /api/v1/admin/feedback, gdy moduładmin_feedbackjest obecny: każdy zalogowany administrator;204, gdy przekaźnik przyjmie wiadomość,429 RATE_LIMITED,502 ADMIN_FEEDBACK_DELIVERY_FAILEDalbo503 ADMIN_FEEDBACK_NOT_CONFIGURED, gdy adres przekaźnika jest pusty albo nie jest adresemhttp(s).
Dla autorów modułów i modułów nakładkowych
Ceny
priceListAdminPortzostał usunięty; zastępuje gopriceBookWritePort— ceny bazowe dla każdej waluty, wskazywane produktem i walutą zamiast id cennika, zapisywane jednym audytowanym poleceniem.@endora-commerce/contractsnie zawiera już reguły stosowania ani dawnych kształtów cennika, przedziałów i macierzy,ListingPricetraci wariantsource: 'product', aPriceListRecord/PriceListReadPortmają nowy kształt dla cenników. Każde usunięcie jest wymienione razem z zamiennikiem w dzienniku zmian pakietu.- Moduł nakładkowy, który dekoruje
pricingService, musi dekorować także zbiorcze punkty wejścia (resolveLinePrices,resolveListingPrices), a nie tylko pojedyncze wywołania; koszyk, kontrola przy składaniu zamówienia i ceny referencyjne zapytań wyceniają przez partię. - Moduł, którego zdarzenia decydują o cenie, podbija
PricingGenerationjak dotąd;bump(change?)przyjmuje terazSurfaceChange, który mówi, co zmiana przesunęła, a podbicie bez niego sprawia, że powierzchnia cen przebudowuje każdy profil. - Własny port wyceny feedów musi implementować
namedListPublishabilityoboknamedListPrices, która przyjmuje terazsalesChannelIdfeedu.
Arytmetyka kwot
@endora-commerce/contractszawiera arytmetykę kwot platformy:money-decimal(roundHalfUp, dokładne sumy i iloczyny,vatByRate— jedyna metoda VAT, dla każdej stawki od sumy kwot netto — oraz jej odwrotnośćnetForGrossAtRate) imoney-input(twoDecimalAmount,twoDecimalAmountString,addAmountPrecisionIssueiERROR_CODES.AMOUNT_PRECISION_EXCEEDED). Moduł nakładkowy, który liczy kwotę z ceny, używa ich zamiastNumber#toFixed(2)czyMath.round(x * 100) / 100, a nowe pole kwoty wpisywanej przez człowieka używa tego zawężenia.- Cena jednostkowa jest przenoszona z czterema miejscami, a zaokrąglana jest wartość pozycji
(decyzja właściciela z 25.10.2026).
@endora-commerce/contractsdodajeunitPriceHalfUp(cena jednostkowa z czterema miejscami, tak jak zapisuje jąnumeric(14,4)),unitPriceFractionDigitsiunitPriceDisplayText(jedna reguła wyświetlania),grossUnitPriceHalfUp, a wmoney-inputfourDecimalUnitPrice,fourDecimalUnitPriceString,isUnitPricePrecisionIssueiERROR_CODES.UNIT_PRICE_PRECISION_EXCEEDED. Moduł nakładkowy, który zapisuje wyliczoną lub wpisaną cenę jednostkową, zachowuje ją przezunitPriceHalfUp— nigdyroundAmountHalfUp, który zaokrągla do dwóch miejsc — a pozycję liczy przezlineAmountHalfUp(unit, quantity).formatMoneyw@endora-commerce/admin-kit/libprzyjmuje{ unitPrice: true }, a wartość dokumentu rodzajumoney—unitPrice: true, by pokazać cenę jednostkową według reguły. OrderReturnContextzyskujevatMethod,productsPaidTotal(zapisane netto + podatek − rabat) idiscountTotal, aOrderReturnContextLinezyskujeunitNetPriceitaxRate;paidLineAmountto suma pozycji zapisana w zamówieniu. ImplementacjaOrderReturnContextPortw module nakładkowym musi podawać te pola i czytać je z tego, co zamówienie zapisało, nigdy ich nie przeliczając.CorrectiveInvoicePortzyskujevalidateCorrection(input), które odrzuca to, co odrzuciłobycreateCorrection, niczego nie zapisując, aCorrectiveInvoiceLinezyskuje opcjonalne polediscount; implementacja w module nakładkowym musi dostarczać tę metodę.- Dwie nowe kolumny dopuszczające
nullzapisują, według której metody powstał dokument:orders.vat_method(per_rateod 0.108.0) iinvoices.vat_summary(podsumowanie według stawek, z którym wystawiono fakturę lub korektę).nulloznacza dokument sprzed aktualizacji; nic nie jest uzupełniane wstecz. Trzecia,return_case_items.default_discount_amount(domyślnie0), zapisuje rabat, który niesie zwrot.
Inne porty i kształty
CatalogProductReadPortzyskujelistPackagingUnitsByProductIds, a jego rekordy —minOrderQuantity,returnableiminOrderUnits: typowana atrapa portu musi je dodać.CartWritePort.addItemsdodaje pozycje partiami do 200 (onBelowMinimum,replaceExisting,keepRepeatedLines); nowyOrderQuoteSourcePort(orderQuoteSourcePort, właścicielorders) mówi, czy pozycje koszyka pochodzące z oferty zachowują zwolnienie.- Klucze odpowiedzi z wartością domyślną (
itemGroups,groupId,positionorazgroupsw danych dokumentu oferty) sprawiają, że wywnioskowane typy ich wymagają: kod budujący literałQuoteRequest,QuoteRequestItem,Order,OrderItemalboQuoteRequestDocumentDatamusi je teraz podać.adminPatchQuoteRequestSchemaiadminCreateQuoteRequestSchemasą obiektami z.superRefine, więc.extend()i.omit()nie są już na nich dostępne. CustomFieldDefinitionRecordwymagacarryTo, aCustomFieldEntityTypeInfomacarryTargets; ręcznie budowana wartość ich potrzebuje.customFieldListFilterPorticustomFieldCarryPortsą nowe.request.responseLanguage()jest dostępne w każdym żądaniu kompozycji, która rejestruje kopertę błędów; ponowna rejestracja koperty z innym resolverem języka zgłasza teraz wyjątek.- Zapisane
grouping: nullw tabeli pozycji szablonu dokumentu oznacza teraz grupy z dokumentu (wcześniej: bez grupowania); tabela, która ma pozostać płaska, zapisuje{ by: 'none' }. - Warstwa panelu
mod-crmma opcjonalne zależności równorzędne@tiptap/reacti@tiptap/starter-kit(^3), które aplikacja panelu już zawiera.
W istniejącym storefroncie
Trasy cen, które wywołuje storefront, zachowują swoje ścieżki, więc storefront utworzony we
wcześniejszym wydaniu nadal pokazuje ceny. Czego nie pokazuje, dopóki nie przeniesie się do niego
plików ze storefrontu wydania: własnej ceny wybranego wariantu na stronie produktu, ceny promocyjnej
pokazanej jako przekreślona cena katalogowa obok Twojej ceny, ceny kupującego jako ceny
porównawczej na kartach produktów i ceny katalogowej obok niższej ceny kupującego. Te pliki to
app/(catalog)/p/[slug]/page.tsx, app/page.tsx, components/ProductCard.tsx,
components/PriceTag.tsx, components/VariantPicker.tsx, components/SearchAutocomplete.tsx,
components/pricing/BaseSalePriceBlock.tsx, components/pricing/PdpPriceToggle.tsx,
lib/api/pricing.ts (gdzie ResolvedPrice.baseListId ma teraz typ string | null) i teksty cen w
lib/i18n/messages.ts. Selektor wariantów wcześniejszego storefrontu pokazywał Price override
wariantu; to pole nie jest już wysyłane, więc selektor nie pokazuje ceny wariantu, dopóki nie
przeniesie się VariantPicker.tsx.
Storefront wydania pokazuje też cenę jednostkową z jej znaczącymi miejscami, do czterech
(0,0125 zł) — karty i wiersze produktów, strona produktu, koszyk, strony zamówienia i zapytania
ofertowego — przez formatMoney(…, { unitPrice: true }) i grossUnitPriceFromNet w
lib/i18n/money.ts, i pozwala wpisać propozycję ceny z czterema miejscami. Storefront utworzony
wcześniej pokazuje taką cenę zaokrągloną do dwóch miejsc, a koszyk nalicza cenę czteromiejscową:
przenieś lib/i18n/money.ts, components/ProductCard.tsx, components/ProductRow.tsx,
components/PriceTag.tsx, components/VariantPicker.tsx, components/SearchAutocomplete.tsx,
components/pricing/BaseSalePriceBlock.tsx, components/CartLine.tsx,
components/orders/OrderItemRows.tsx, components/rfq/RfqDraftView.tsx,
app/(account)/quote-requests/[id]/page.tsx i lib/analytics/ecommerce.ts.
Storefront wydania pokazuje też minimalne ilości zamówienia (strona produktu, karta, koszyk,
finalizacja zamówienia, szybkie zamówienie, szkic zapytania ofertowego, lista zakupów), to, czy
pozycja podlega zwrotowi, w formularzu zwrotu, oraz grupy pozycji oferty i zamówienia. Storefront
utworzony wcześniej tego nie robi: jego kupujący trafiają na odmowy serwera —
422 PRODUCT_BELOW_MINIMUM_QUANTITY, 422 RETURN_LINE_NOT_RETURNABLE — z komunikatem serwera i
widzą pozycje zamówienia albo oferty bez grup, w kolejności wyświetlania. Zmiany są we wzorcowym
storefroncie między v0.107.0 a v0.108.0: wymienia je git diff v0.107.0 v0.108.0 -- storefront
w repozytorium platformy.
Tę sekcję napisano 23, 24 i 25.10.2026 na podstawie gałęzi wydania zmiany cen
(feat/160-pricing-model-v2) i gałęzi master od v0.107.0, sprawdzając opisy zmian
(changesety) wydania z jego migracjami, manifestami, trasami i historią wzorcowego storefrontu. W chwili pisania żadna instancja nie została zaktualizowana
poleceniem pnpm run upgrade 0.108.0; kolejność kroków i zmiany w storefroncie nie były
wypróbowane.
Po aktualizacji do wydania 0.109.0
Każdy wpis poniżej jest samodzielny: przeczytaj te, które dotyczą używanych przez ciebie części platformy.
Feedy produktowe: cena poniżej jednego grosza jest publikowana jako opakowanie
- Co się zmieniło. Do tego wydania feed produktowy zaokrąglał każdą cenę do dwóch miejsc po przecinku, więc produkt w cenie 0,0125 PLN trafiał do pliku jako 0,01 PLN, podczas gdy strona produktu pokazywała 0,0125 zł — niezgodność ceny, za którą Google Merchant Center odrzuca produkty. Cena z więcej niż dwoma znaczącymi miejscami po przecinku jest teraz publikowana jako cena opakowania 10, 100, 1 000 albo 10 000 sztuk — najmniejszego, przy którym jest dokładna co do grosza: 0,0125 PLN trafia do pliku jako 100 sztuk za 1,25 PLN (1,54 PLN w feedzie brutto przy 23 %). Wielkość opakowania trafia do pól opakowania szablonu. Cena z dwoma miejscami po przecinku jest publikowana dokładnie tak jak wcześniej, bajt w bajt.
- Predefiniowany szablon Google Merchant Center dostaje trzy pola, po
g:sale_price:g:multipack,g:unit_pricing_measureig:unit_pricing_base_measure. Na istniejącej instancji dodaje je do zainstalowanego szablonu migracjaMigration20261025T145041ProductFeedsGoogleTemplatePackFields. Dla każdego produktu z ceną co do grosza są puste i pomijane w pliku. - Produkt, którego szablon nie potrafi opublikować jako opakowania, jest pomijany w feedzie,
podczas gdy wcześniej trafiał do niego z zaokrągloną ceną. Dotyczy to każdego takiego produktu w
feedzie na szablonie Meta, Amazon, eBay albo Allegro oraz w feedzie na własnej kopii szablonu
Google. Wynik generowania wymienia każdy z nich pod nowym powodem
sub_cent_price_without_pack_field. Co zrobić: po aktualizacji otwórz ostatnie generowanie każdego feedu; jeśli ten powód się pojawia, a produkt powinien być w tym feedzie, dodaj do szablonu pole powiązane ze źródłem Wielkość opakowania (ceny poniżej grosza) — w kopii szablonu Google jakog:multipack, zg:unit_pricing_measureig:unit_pricing_base_measurepowiązanymi z dwoma źródłami opakowania jako liczby sztuk. Generowanie, które pomija więcej niż połowę pozycji, kończy się błędemskip_threshold_exceededi dalej udostępnia poprzedni plik — który wciąż ma zaokrąglone ceny — więc nie zostawiaj takiego błędu bez reakcji. - Strona docelowa. Strona produktu w storefroncie pokazuje cenę jednej sztuki. Zasady Google dla opakowań zbiorczych wymagają, by strona docelowa pokazywała cenę opakowania, więc obserwuj diagnostykę Merchant Center dla produktów publikowanych jako opakowania.
- Dla klientów API i integratorów:
feedFieldSourceKindSchemadostajepack_quantity,pack_unit_pricing_measureipack_unit_pricing_base_measure, afeedRunIssueReasonSchema—sub_cent_price_without_pack_fieldisub_cent_price_beyond_pack_limit. Klient, który sprawdza szablon, wyeksportowany dokument szablonu albo problemy generowania względem zamkniętej listy, musi je przyjmować. - Dla autorów modułów nakładkowych:
FeedItemPricedostajetaxRatePercent: number | null, aResolveFeedItemInput— opcjonalnetemplateFields. Moduł nakładkowy, który zastępuje resolver pól pozycji, dostaje oba i publikuje to, co sam zapisze: aby opublikować cenę poniżej grosza jako opakowanie, stosuje tę samą regułę, zfeedPackQuantityzservices/feed-pack.ts.
Oferty: rabat poniżej jednego grosza jest drukowany jako „—”
- W PDF-ie oferty i w podglądzie edytora szablonów dokumentów rabat pozycji albo suma rabatów, które są rzeczywiste, ale zaokrąglają się do 0,00 — na przykład 0,0001 PLN na sztuce przy trzech sztukach — są drukowane jako „—” zamiast „0,00 zł”, które czytało się jako „brak rabatu”. Sama kwota się nie zmienia, więc na dokumencie nadal wartość katalogowa − rabat = netto. Nic nie trzeba robić.
- Na ekranie zapytania ofertowego w panelu administracyjnym kolumna Różnica pokazuje różnicę cen jednostkowych z jej znaczącymi miejscami, do czterech — −0,0001 PLN — zamiast zaokrąglać ją do 0,00.
- Dla autorów modułów i modułów nakładkowych: wartość dokumentu rodzaju
moneymoże miećbelowMinorUnit: true.formatDocumentValuedrukuje ją jako „—”, asumDocumentValueszachowuje ten znacznik, gdy suma nadal wynosi zero; własny blok, który formatuje wartości inaczej, powinien robić to samo.
Faktury, płatności i sumy zamówień: zasady kwot ze zgłoszenia #428
Dla operatorów.
- Faktura uwzględnia każdy zwrot wykonany przed jej wystawieniem (zgłoszenie #428). Do tego
wydania faktura VAT odejmowała od zapłaconej kwoty tylko zwroty zgłoszone przez bramkę płatności.
Teraz odejmuje także zwrot towaru rozliczony przelewem, dla zamówienia nieopłaconego przez bramkę,
oraz zwrot rozliczony uznaniem limitu kredytowego; zamówienie, które operator oznaczył jako
zwrócone, liczy się jako zwrócone w całości — bez pobranej płatności, a także z pobraną płatnością
zwróconą ręcznie (na przykład po anomalii zwrotu, niżej), nigdy nie dodając oznaczenia do zwrotu
przez bramkę, który już zapisano. Zamówienie zwrócone w ten
sposób w całości przed wystawieniem faktury nie jest już fakturowane — wystawienie zwraca
422 INVOICE_ORDER_REFUNDED, a automatyczne wystawianie je pomija i zapisuje powód w logu — a zwrócone częściowo jest fakturowane tylko z zatrzymaną kwotą jako zapłaconą. Faktury wystawione przed aktualizacją się nie zmieniają. Gdy modułreturnsjest wyłączony, odejmowane są tylko zwroty z bramki. - Faktura nigdy nie pokazuje ujemnej kwoty do zapłaty (zgłoszenie #428). Gdy zapłacono więcej niż kwota brutto faktury, do zapłaty jest 0, a różnica jest pokazana jako nadpłata — wiersz na ekranie faktury i linia Nadpłata na PDF. Szablon faktury edytowany przed aktualizacją też drukuje tę linię, z etykietą Nadpłata; etykietę i przełącznik ukrywający linię ma blok podsumowania w edytorze szablonów. Korekta, której kwota brutto jest ujemna, pokazuje teraz 0 do zapłaty, a kwotę do zwrotu jako nadpłatę.
- Minimalna wartość zamówienia ma najwyżej dwa miejsca po przecinku (zgłoszenie #428). Zapisanie
orders.min_order_valuez większą liczbą miejsc jest odrzucane z400 AMOUNT_PRECISION_EXCEEDED, jak każda inna wpisywana kwota; zapisana już wartość jest odczytywana jak dotąd. Nic nie trzeba robić, chyba że jakiś skrypt zapisuje to ustawienie z większą liczbą miejsc. - Kwota zwrotu z bramki z więcej niż dwoma miejscami po przecinku jest odrzucana, a nie
zaokrąglana (zgłoszenie #428). Płatność zachowuje zwróconą kwotę i status, odmowa trafia do logu
z płatnością, zamówieniem, zgłoszoną kwotą i identyfikatorem zwrotu w bramce, a historia płatności
zawiera
providerDetails.refundAnomaly. Webhook bramki i tak zostaje potwierdzony, więc nie jest wysyłany ponownie: sprawdź taki zwrot w bramce i rozlicz go ręcznie. - Podsumowanie grupy pozycji zamówienia liczy VAT tą samą metodą co zamówienie (zgłoszenie #428). Podsumowania grup na ekranie zamówienia w panelu brały kwotę brutto złożonego zamówienia jako sumę wartości pozycji, z których każda jest zaokrąglona osobno, co mogło różnić się o grosz od VAT liczonego według stawek, który zamówienie naliczyło. Teraz biorą VAT według stawek od sumy kwot netto grupy; wartości pozycji pozostają takie jak dotąd.
Dla klientów API i integratorów.
- Faktura zawiera
overpayment, aamountDuenigdy nie jest ujemne (zgłoszenie #428): wGET /api/v1/admin/invoices/:idi każdej odpowiedzi ze szczegółami fakturyamountDuetomax(0, grossTotal − paidTotal), a nowe poleoverpaymenttomax(0, paidTotal − grossTotal). Klient, który odczytywał ujemneamountDuejako nadpłatę, odczytuje terazoverpayment. Pole scalające szablonu{{var invoice.overpayment}}jest nowe, obok{{var invoice.amountDue}}. paidTotalfaktury wystawionej po aktualizacji odejmuje zwroty wykonane pozapayments, aPOST /api/v1/admin/orders/:orderId/invoiceszwraca422 INVOICE_ORDER_REFUNDEDdla zamówienia zwróconego w ten sposób w całości — zob. punkt dla operatorów powyżej.- Moduł bramki wywołujący
receivePaymentPort.reflectRefundz więcej niż dwoma miejscami po przecinku dostaje{ paymentId, changed: false }i nic nie zostaje zapisane, podczas gdy wcześniej kwota była zaokrąglana połówkowo w górę. Podawaj kwotę z bramki w jednostkach głównych, z najwyżej dwoma miejscami po przecinku.
W istniejącym storefroncie.
Storefront z tego wydania bierze podsumowanie grupy pozycji na stronie zamówienia z VAT według
stawek od kwot netto grupy (orderSectionTotals w components/line-groups/GroupedLineRows.tsx,
wywoływane przez components/orders/OrderItemRows.tsx), podczas gdy wcześniejszy storefront sumuje
wartości pozycji i może pokazać podsumowanie różniące się o grosz od VAT zamówienia. Przenieś te
dwa pliki, aby to wyrównać; bez nich nic się nie psuje.
Ten wpis został napisany 2026-10-25 na podstawie master w 9e43c3412 i gałęzi zgłoszenia #428
(fix/428-money-followups). W chwili jej pisania żadna instancja nie była zaktualizowana do
0.109.0.
Instancja niespójna od początku
Instalator 0.101.x lub starszy zapisywał każdy pakiet wydania z ^ poza
@endora-commerce/contracts. Uruchomiony po opublikowaniu nowszego wydania poprawkowego,
instalował to wydanie poprawkowe każdego pakietu poza @endora-commerce/contracts, który
zostawał przy starszym; instalacja wypisuje unmet peer @endora-commerce/contracts raz na moduł.
pnpm run upgrade — albo, przed tym poleceniem, dwa polecenia powyżej — ustawia wszystkie
pakiety na jedną wersję. Późniejsze instalatory zapisują każdy pakiet wydania dokładnie w wersji,
którą instalujesz, więc nowa instancja nigdy nie jest niespójna.
Bez CLI
To, co robi polecenie, można zrobić ręcznie: w każdym package.json instancji i storefrontu
ustaw każdy pakiet @endora-commerce/* należący do wydania na nową wersję, zachowując ^
i dokładne przypięcia; usuń pnpm-lock.yaml i uruchom pnpm install (co przesuwa też Twoje
pozostałe zależności w ramach ich zakresów); potem uruchom pnpm run setup, a w repozytorium
storefrontu pnpm install.