Cenniki

Daj firmom i klientom ich własne ceny - procent od ceny katalogowej plus stałe ceny produktów - a Twój sklep dalej trzyma katalog w cache.

Cennik - Gold, Silver, Dealer - daje grupie kupujących ich własne ceny. Przypisujesz go firmie albo klientowi, a cmssy wycenia z niego koszyk, checkout i zamówienia tego kupującego, po stronie serwera. Katalog, który czyta odwiedzający, się nie zmienia, więc strony produktów zostają anonimowe i cache'owalne.

Co zawiera cennik

  • Procent od ceny katalogowej, od 0 do 99,99, najwyżej dwa miejsca po przecinku. Działa na każdy produkt, dla którego cennik nie ma stałej ceny, i na każdy próg ilościowy tego produktu. Jest przechowywany w punktach bazowych i liczony na liczbach całkowitych: zaokrąglenie połówkowe w górę do pełnej jednostki minor, darmowy produkt zostaje na 0, a płatny nigdy nie spada poniżej 1 jednostki minor.
  • Stałe ceny dla produktów, w jednostkach minor, każda z opcjonalnymi progami ilościowymi. Produkt z wariantami dostaje stałą cenę na wariant, nigdy jedną na cały produkt. Stała cena może być wyższa od katalogowej - cena kontraktowa to też cena.

Stała cena wygrywa z procentem; gdy nie ma żadnego z nich, kupujący płaci cenę katalogową. Walutą jest zawsze waluta produktu - cennik zmienia kwoty, nie waluty. Stała cena dla produktu lub wariantu, który później usunięto, niczego nie wycenia, a cennik oznacza ją jako usuniętą, żebyś mógł ją skasować.

Kto płaci według którego cennika

Jeden cennik na kupującego, nigdy dwa naraz:

  • Klient należący do firmy zawsze płaci według cennika firmy - albo cenę katalogową, gdy firma żadnego nie ma. cmssy odmawia przypisania cennika bezpośrednio takiemu klientowi: przypisz go firmie. Cennik, który klient miał przed dołączeniem do firmy, jest ignorowany, dopóki do niej należy.
  • Klient bez firmy może mieć własny cennik - VIP albo pojedynczy kupiec hurtowy.
  • Gość płaci cenę katalogową.

Zarządzanie cennikami

Cenniki są w Commerce → Cenniki. Utwórz cennik z nazwą i procentem, potem otwórz go i dodaj stałe ceny produktów lub wariantów. Usunięcie cennika kasuje jego stałe ceny i odpina go od wszystkich firm i klientów; zamówienia zachowują nazwę, według której je wyceniono.

Cennik przypisujesz na stronie klienta albo na rekordzie firmy w jej modelu. Uprawnienia:

  • models:edit - tworzenie, edycja i usuwanie cenników oraz ich stałych cen; models:view pozwala je czytać.
  • customers:manage - przypisanie cennika klientowi lub firmie. customers:view wystarczy, żeby widzieć, jakie cenniki istnieją.

Synchronizacja z ERP zapisuje stałe ceny hurtowo: priceList.setEntries w admin API zapisuje do 1000 naraz, wszystko albo nic - jeden zły wiersz odrzuca całe wywołanie, a błąd wskazuje każdy zły wiersz po indeksie.

Ze Spotlight i serwera MCP

Spotlight i serwer MCP (od 0.108.0) mają te same operacje, z tymi samymi uprawnieniami: list_price_lists, get_price_list, create_price_list, update_price_list, delete_price_list, set_price_list_entry, set_price_list_entries, remove_price_list_entries, set_customer_price_list i set_company_price_list.

Ceny w sklepie headless

Cena kupującego jest osobista, więc nigdy nie trafia do zapytania o produkt - każdy odczyt katalogu stałby się zależny od członka i nie dałby się cache'ować. Zamiast tego:

  • Renderuj i cache'uj katalog anonimowo. Listy i strony produktów czytają public.model.records dokładnie tak jak dla niezalogowanego odwiedzającego, z cenami katalogowymi. Nigdy nie kluczuj strony w cache po członku.
  • Po zalogowaniu nałóż ceny kupującego. Zapytaj account.prices o produkty na ekranie - najwyżej 100 pozycji na wywołanie - z nagłówkiem Authorization: Bearer <accessToken> członka. Bez tokenu członka wywołanie jest odrzucane.
  • Nigdy nie cache'uj tej odpowiedzi dla innego odwiedzającego. Żadnego współdzielonego cache fetch, żadnego CDN. Czytaj ją w dynamicznej trasie albo server action i wstaw ceny do już wyrenderowanej strony.
query BuyerPrices($workspaceId: ID!, $items: [PriceQueryItem!]!) {
  account {
    prices(workspaceId: $workspaceId, items: $items) {
      recordId
      variantKey
      currency
      price
      priceMax
      listed
      tiers { minQty price }
    }
  }
}

Każda pozycja to { recordId, variantKey }, gdzie variantKey to ProductVariant.id z API dostawczego. Pomiń go przy produkcie z wariantami, żeby dostać zakres: price to najniższa cena, priceMax najwyższa, a tiers jest puste - cena "od" na karcie listingu. Kwoty są w jednostkach minor. listed jest true, gdy cenę ustawił cennik - pod etykietę "Twoja cena". Nieznany lub ukryty produkt jest pomijany, więc dopasowuj wiersze po recordId i variantKey, nie po pozycji.

Pod plakietkę typu "Twoje ceny: Dealer Gold" przeczytaj nazwę cennika z profilu członka. Jest null, gdy żaden cennik nie obowiązuje, a członek widzi zawsze tylko swój:

query MyPriceList($workspaceId: ID!) {
  account {
    profile(workspaceId: $workspaceId) {
      priceList { name }
    }
  }
}

Z @cmssy/core 19.11.0 lub nowszym zapytanie o ceny wysyła za Ciebie getBuyerPrices.

Koszyk i checkout

Koszyk jest wyceniany dla tego, kto go czyta. Przy każdym odczycie i zapisie koszyka cmssy przelicza linie wycenione według innego cennika - gość, który się zalogował, członek, który się wylogował, cennik przypisany lub odpięty. unitPrice to kwota, która zostanie pobrana; snapshot.basePrice to cena katalogowa przed cennikiem i progami ilościowymi, pod przekreśloną cenę "było". Jest null na linii dodanej, zanim istniały cenniki.

query CartPrices($workspaceId: ID!) {
  cart {
    get(workspaceId: $workspaceId) {
      items {
        id
        quantity
        unitPrice
        currentPrice
        priceMismatch
        snapshot { price basePrice currency }
      }
      subtotal
    }
  }
}

Zmiana procentu lub stałych cen cennika nie przelicza koszyków już wycenionych z tego cennika. Linia pokazuje nową cenę w currentPrice z priceMismatch: true, a checkout pobiera to, co jest w koszyku.

Gdy checkout odmawia

Jeśli przeliczenie zmieniło koszyk od chwili, gdy kupujący go ostatnio widział, cart.checkout odmawia, zamiast pobrać kwotę, której nikt nie pokazał. Powód jest w extensions.reason błędu GraphQL:

  • CART_REPRICED (kod BAD_USER_INPUT) - ceny kupującego się zmieniły. Koszyk jest już zapisany z nowymi cenami: przeczytaj go ponownie, pokaż nowe sumy, poproś kupującego o potwierdzenie i wywołaj cart.checkout jeszcze raz.
  • CART_CURRENCY_CHANGED (kod CONFLICT) - linia byłaby teraz wyceniona w innej walucie niż koszyk; wskazuje ją extensions.itemId. Usuń tę linię i dodaj produkt ponownie.

Z @cmssy/core 19.11.0 lub nowszym checkoutErrorReason(error) zwraca każdy z tych powodów.

Kody rabatowe działają na cenę z cennika: rabat odejmuje się od sumy wyliczonej z cennika i do niej porównuje się minimalną wartość koszyka kodu.

Zamówienia

Zamówienie zamraża cennik, z którego je wyceniono. Jego nazwa jest widoczna na zamówieniu w panelu i w get_order i zostaje po zmianie nazwy lub usunięciu cennika. Na każdej pozycji zamówienia:

  • price - pobrana cena jednostkowa, z cennikiem i progiem ilościowym.
  • listPrice - katalogowa cena jednostkowa przed cennikiem i progami ilościowymi. Bez cennika to cena przed progami, jak dotąd; przy cenie kontraktowej powyżej katalogowej może być niższa niż price.

Edycja zamówienia w panelu przelicza jego linie produktowe według aktualnych wartości zamrożonego cennika, albo po cenach katalogowych, jeśli cennik usunięto. Ręczne zamówienie jest wyceniane według cennika wybranego klienta.

Następne kroki