@spree/docs 0.1.182 → 0.1.184

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/developer/core-concepts/addresses.md +106 -198
  2. package/dist/developer/core-concepts/architecture.md +97 -126
  3. package/dist/developer/core-concepts/calculators.md +75 -252
  4. package/dist/developer/core-concepts/carts.md +1 -1
  5. package/dist/developer/core-concepts/catalogs.md +140 -0
  6. package/dist/developer/core-concepts/channels.md +0 -4
  7. package/dist/developer/core-concepts/commissions.md +253 -0
  8. package/dist/developer/core-concepts/companies.md +240 -0
  9. package/dist/developer/core-concepts/customers.md +0 -3
  10. package/dist/developer/core-concepts/discounts.md +133 -0
  11. package/dist/developer/core-concepts/events.md +83 -576
  12. package/dist/developer/core-concepts/fees.md +144 -0
  13. package/dist/developer/core-concepts/imports-exports.md +105 -679
  14. package/dist/developer/core-concepts/inventory.md +114 -248
  15. package/dist/developer/core-concepts/markets.md +9 -12
  16. package/dist/developer/core-concepts/media.md +127 -16
  17. package/dist/developer/core-concepts/metafields.md +123 -200
  18. package/dist/developer/core-concepts/order-totals.md +110 -0
  19. package/dist/developer/core-concepts/orders.md +1 -1
  20. package/dist/developer/core-concepts/payments.md +11 -14
  21. package/dist/developer/core-concepts/pricing.md +11 -13
  22. package/dist/developer/core-concepts/products.md +191 -62
  23. package/dist/developer/core-concepts/promotions.md +12 -11
  24. package/dist/developer/core-concepts/search-filtering.md +2 -4
  25. package/dist/developer/core-concepts/sellers.md +210 -0
  26. package/dist/developer/core-concepts/staff-roles.md +56 -23
  27. package/dist/developer/core-concepts/store-credits-gift-cards.md +0 -3
  28. package/dist/developer/core-concepts/taxes.md +125 -113
  29. package/dist/developer/core-concepts/translations.md +61 -68
  30. package/dist/developer/core-concepts/webhooks.md +25 -59
  31. package/dist/developer/how-to/custom-promotion.md +3 -3
  32. package/package.json +1 -1
  33. package/dist/developer/core-concepts/companies-and-catalogs.md +0 -81
  34. package/dist/developer/core-concepts/taxes-discounts-fees.md +0 -199
@@ -1,303 +1,126 @@
1
1
  ---
2
2
  title: Calculators
3
+ description: The small pieces of arithmetic behind delivery charges and promotion discounts — what each one does and how to choose between them.
3
4
  ---
4
5
 
5
6
  ## Overview
6
7
 
7
- ### Calculator Model Diagram
8
-
9
- ```mermaid
10
- erDiagram
11
- Calculator {
12
- string type
13
- string calculable_type
14
- text preferences
15
- }
16
-
17
- TaxRate {
18
- string name
19
- decimal amount
20
- boolean included_in_price
21
- }
22
-
23
- ShippingMethod {
24
- string name
25
- string display_on
26
- }
27
-
28
- PromotionAction {
29
- string type
30
- }
31
-
32
- Adjustment {
33
- decimal amount
34
- string label
35
- }
36
-
37
- TaxRate ||--|| Calculator : "has one"
38
- ShippingMethod ||--|| Calculator : "has one"
39
- PromotionAction ||--|| Calculator : "has one"
40
- TaxRate ||--o{ Adjustment : "creates"
41
- ShippingMethod ||--o{ ShippingRate : "calculates"
42
- PromotionAction ||--o{ Adjustment : "creates"
43
- ```
44
-
45
- **Key relationships:**
46
- - **Calculator** computes amounts for various features
47
- - Used by **[Tax Rates](taxes.md)** to calculate tax amounts
48
- - Used by **[Shipping Methods](fulfillments.md)** to calculate shipping costs
49
- - Used by **[Promotion Actions](promotions.md)** to calculate discounts
50
- - Calculators store [preferences (rates, percentages, etc.)](../customization/model-preferences.md) for their calculations
51
-
52
- Spree makes extensive use of the `Spree::Calculator` model and there are several subclasses provided to deal with various types of calculations flat rate, percentage discount, sales tax, VAT, etc. All calculators extend the `Spree::Calculator` class and must provide the following methods:
53
-
54
- ```ruby
55
- def self.description
56
- # Human readable description of the calculator
57
- end
58
-
59
- def compute(object=nil)
60
- # Returns the value after performing the required calculation
61
- end
62
- ```
63
-
64
- Calculators link to a `calculable` object, which are typically one of `Spree::ShippingMethod`, `Spree::TaxRate`, or `Spree::Promotion::Actions::CreateAdjustment`. These three classes use the `Spree::CalculatedAdjustments` module described below to provide an easy way to calculate adjustments for their objects.
65
-
66
- ## Available Calculators
67
-
68
- The following are descriptions of the currently available calculators in Spree. If you would like to add your own, please see the [Creating a New Calculator](#creating-a-new-calculator) section.
69
-
70
- ### Default Tax
71
-
72
- For information about this calculator, please read the [Taxes](taxes.md) guide.
73
-
74
- ### Flat Percent Per Item Total
75
-
76
- This calculator has one preference: `flat_percent` and can be set like this:
77
-
78
- ```ruby
79
- calculator.preferred_flat_percent = 10
80
- ```
81
-
82
- This calculator takes an order and calculates an amount using this calculation:
83
-
84
- ```ruby
85
- [item total] x [flat percentage]
86
- ```
87
-
88
- For example, if an order had an item total of `$31` and the calculator was configured to have a flat percent amount of `10`, the discount would be `$3.10`, because `$31 x 10% = $3.10`.
89
-
90
- ### Flat Rate
91
-
92
- This calculator can be used to provide a flat rate discount.
93
-
94
- This calculator has two preferences: `amount` and `currency`. These can be set like this:
95
-
96
- ```ruby
97
- calculator.preferred_amount = 10
98
- calculator.preferred_currency = "USD"
99
- ```
100
-
101
- The currency for this calculator is used to check to see if a shipping method is available for an order. If an order's currency does not match the shipping method's currency, then that shipping method will not be displayed on the frontend.
102
-
103
- This calculator can take any object and will return simply the preferred amount.
104
-
105
- ### Flexi Rate
106
-
107
- This calculator is typically used for promotional discounts when you want a specific discount for the first product, and then subsequent discounts for other products, up to a certain amount.
8
+ A calculator works out an amount. "Charge $5 per item." "Take 15% off." "Free over $50, otherwise $7."
108
9
 
109
- This calculator takes three preferences:
10
+ They exist because the rule and the number are different questions. A promotion decides *whether* a discount applies; a calculator decides *how much*. Keeping them apart means a merchant can change "10% off" to "$10 off" without touching the conditions that decide who qualifies.
110
11
 
111
- * `first_item`: The discounted price of the first items.
112
- * `additional_item`: The discounted price of subsequent items.
113
- * `max_items`: The maximum number of items this discount applies to.
12
+ Two things use calculators:
114
13
 
115
- The calculator computes based on this:
14
+ - **[Delivery methods](fulfillments.md)** what delivery costs
15
+ - **[Promotions](promotions.md)** — how big a discount is
116
16
 
117
- ```text
118
- [first item discount] + (([items_count*] - 1) x [additional item discount])
119
- ```
120
-
121
- * up to the `max_items`
122
-
123
- Thus, if you have ten items in your shopping cart, your `first_item` preference is set to `$10`, your `additional_items` preference is set to `$5`, and your `max_items` preference is set to `4`, the total discount would be `$25`:
124
-
125
- * `$10` for the first item
126
- * `$5` for each of the `3` subsequent items: `$5 \* 3 = $15`
127
- * `$0` for the remaining `6` items
128
-
129
- ### Per Item
130
-
131
- The Per Item calculator (`Spree::Calculator::Shipping::PerItem`) is a shipping calculator that charges a flat amount for every item in a shipment.
132
-
133
- This calculator takes two preferences:
134
-
135
- * `amount`: The flat amount charged per item.
136
- * `currency`: The currency for this calculator.
137
-
138
- It computes a flat rate per item by multiplying the `amount` preference by the total item quantity in the shipment package:
139
-
140
- ```text
141
- [amount] x [total item quantity in package]
142
- ```
143
-
144
- For example, with an `amount` of `5` and a package containing `3` items in total, the calculator computes an amount of `15` (`5 x 3`).
145
-
146
- ### Percent Per Item
147
-
148
- The Percent Per Item calculator (`Spree::Calculator::PercentOnLineItem`) applies a percentage discount to a single line item. It takes two preferences:
17
+ > **INFO:** Tax does **not** use calculators. Tax is worked out by a [tax provider](taxes.md), which can be Spree's own rate tables or an external service.
149
18
 
150
- * `percent`: The percentage to apply to the line item's amount.
151
- * `apply_only_on_full_priced_items`: When enabled, skips line items that are already on sale.
19
+ ## Choosing a calculator
152
20
 
153
- For each line item, the calculator computes `line item amount` x `percent` / `100`, capped at the line item's amount so a promotion adjustment never pushes the total negative.
21
+ Each calculator takes a few settings — the amount, the percentage, the threshold. A merchant picks one and fills in the settings when setting up a delivery method or a promotion.
154
22
 
155
- For example, a `$30` line item at `10%` yields a `$3` discount (`$30 x 10% = $3`).
23
+ ### For delivery charges
156
24
 
157
- ### Price Sack
25
+ | Calculator | What it charges | Settings |
26
+ |---|---|---|
27
+ | **Flat rate** | The same amount every time | `amount`, `currency` |
28
+ | **Per item** | An amount for each item in the parcel | `amount`, `currency` |
29
+ | **Percent of item total** | A percentage of what's in the parcel | `flat_percent` |
30
+ | **Price sack** | One amount above a threshold, another below — the usual "free delivery over $50" | `minimal_amount`, `discount_amount`, `normal_amount` |
31
+ | **Flexi rate** | A charge for the first item, less for each one after | `first_item`, `additional_item`, `max_items` |
32
+ | **Digital delivery** | Nothing, for downloads | — |
158
33
 
159
- The Price Sack calculator is useful for when you want to provide a discount for an order which is over a certain price. The calculator has four preferences:
34
+ ### For promotion discounts
160
35
 
161
- * `minimal_amount`: The minimum amount for the line items total to trigger the calculator.
162
- * `discount_amount`: The amount to discount from the order if the line items total is equal to or greater than the `minimal_amount`.
163
- * `normal_amount`: The amount to discount from the order if the line items total is less than the `minimal_amount`.
164
- * `currency`: The currency for this calculator. Defaults to the store currency
36
+ | Calculator | What it takes off | Settings |
37
+ |---|---|---|
38
+ | **Flat rate** | A fixed amount | `amount`, `currency` |
39
+ | **Percent of item total** | A percentage of the order | `flat_percent` |
40
+ | **Percent on line item** | A percentage of one item | `percent` |
41
+ | **Flexi rate** | A sliding amount by quantity | `first_item`, `additional_item`, `max_items` |
42
+ | **Tiered percent** | A percentage that grows with order value | tiers |
43
+ | **Tiered flat rate** | A fixed amount that grows with order value | tiers |
165
44
 
166
- Suppose you have a Price Sack calculator with a `minimal_amount` preference of `$50`, a `normal_amount` preference of `$2`, and a `discount_amount` of `$5`. An order with a line items total of `$60` would result in a discount of `$5` for the whole order. An order of `$20` would result in a discount of `$2`.
45
+ ## Worked examples
167
46
 
168
- ## Creating a New Calculator
47
+ <details>
48
+ <summary>Free delivery over $50, otherwise $7</summary>
169
49
 
170
- To create a new calculator for Spree, you need to do two things. The first is to inherit from the `Spree::Calculator` class and define `description` and `compute` methods on that class:
50
+ **Price sack**, with `minimal_amount: 50`, `discount_amount: 0`, `normal_amount: 7`.
171
51
 
172
- ```ruby
173
- class CustomCalculator < Spree::Calculator
174
- def self.description
175
- # Human readable description of the calculator
176
- end
52
+ A $60 basket pays nothing. A $20 basket pays $7.
177
53
 
178
- def compute(object=nil)
179
- # Returns the value after performing the required calculation
180
- end
181
- end
182
- ```
54
+ </details>
183
55
 
184
- If you are creating a new calculator for shipping methods, please be aware that you need to inherit from `Spree::ShippingCalculator` instead, and define a `compute_package` method:
56
+ <details>
57
+ <summary>$3 to ship the first item, $1 for each extra</summary>
185
58
 
186
- ```ruby
187
- class CustomCalculator < Spree::ShippingCalculator
188
- def self.description
189
- # Human readable description of the calculator
190
- end
59
+ **Flexi rate**, with `first_item: 3`, `additional_item: 1`.
191
60
 
192
- def compute_package(package)
193
- # Returns the value after performing the required calculation
194
- end
195
- end
196
- ```
61
+ Four items cost `$3 + (3 × $1)` = **$6**. Set `max_items` to stop charging beyond a point.
197
62
 
198
- The second thing is to register this calculator as a tax, shipping, or promotion adjustment calculator by calling code like this at the end of `config/initializers/spree.rb` inside your application `config` variable defined for brevity:
63
+ </details>
199
64
 
200
- **Spree 5.2+:**
65
+ <details>
66
+ <summary>10% off the order</summary>
201
67
 
202
- ```ruby config/initializers/spree.rb
203
- Rails.application.config.after_initialize do
204
- Spree.calculators.tax_rates << CustomCalculator
205
- Spree.calculators.shipping_methods << CustomCalculator
206
- Spree.calculators.promotion_actions_create_adjustments << CustomCalculator
207
- end
208
- ```
68
+ **Percent of item total**, with `flat_percent: 10`.
209
69
 
210
- **Spree 5.1 and below:**
70
+ A $31 order gets **$3.10** off.
211
71
 
212
- ```ruby config/initializers/spree.rb
213
- Rails.application.config.spree.calculators.tax_rates << CustomCalculator
214
- Rails.application.config.spree.calculators.shipping_methods << CustomCalculator
215
- Rails.application.config.spree.calculators.promotion_actions_create_adjustments << CustomCalculator
216
- ```
72
+ </details>
217
73
 
74
+ <details>
75
+ <summary>Spend more, save more</summary>
218
76
 
219
- For example if your calculator is placed in `app/models/spree/calculator/shipping/my_own_calculator.rb` you should call:
77
+ **Tiered percent** 5% over $100, 10% over $250, 15% over $500.
220
78
 
221
- **Spree 5.2+:**
79
+ The order total picks the tier. One promotion covers the whole ladder instead of three competing ones.
222
80
 
223
- ```ruby config/initializers/spree.rb
224
- Rails.application.config.after_initialize do
225
- Spree.calculators.shipping_methods << Spree::Calculator::Shipping::MyOwnCalculator
226
- end
227
- ```
81
+ </details>
228
82
 
229
- **Spree 5.1 and below:**
230
83
 
231
- ```ruby config/initializers/spree.rb
232
- Rails.application.config.spree.calculators.shipping_methods << Spree::Calculator::Shipping::MyOwnCalculator
233
- ```
84
+ ## Setting one up
234
85
 
86
+ Calculators are configured as part of the thing that uses them. The available calculators for each are discoverable, so a dashboard or a script can present the real list rather than a hardcoded one:
235
87
 
236
- ### Determining Availability
237
88
 
238
- By default, all shipping method calculators are available at all times. If you wish to make this dependent on something from the order, you can re-define the `available?` method inside your calculator:
89
+ ```typescript Admin SDK
90
+ // What can price a delivery method?
91
+ const calculators = await adminClient.deliveryMethods.calculators()
239
92
 
240
- ```ruby app/models/custom_calculator.rb
241
- class CustomCalculator < Spree::Calculator
242
- def available?(object)
243
- object.currency == "USD"
244
- end
245
- end
93
+ // Set up a delivery method with a flat rate
94
+ await adminClient.deliveryMethods.create({
95
+ name: 'Standard shipping',
96
+ delivery_zone_id: 'dz_xxx',
97
+ calculator_type: 'Spree::Calculator::Shipping::FlatRate',
98
+ calculator_attributes: {
99
+ preferences: { amount: '7.00', currency: 'USD' },
100
+ },
101
+ })
246
102
  ```
247
103
 
248
- ## Calculated Adjustments
249
-
250
- If you wish to use Spree's calculator functionality for your own application, you can include the `Spree::CalculatedAdjustments` module into a model of your choosing.
251
-
252
- ```ruby app/models/plan.rb
253
- class Plan < ActiveRecord::Base
254
- include Spree::CalculatedAdjustments
255
- end
104
+ ```bash CLI
105
+ spree api get /delivery_methods/calculators
256
106
  ```
257
107
 
258
- To have calculators available for this class, you will need to register them. `Spree.calculators` is a fixed-member struct (`SpreeCalculators`, defined in `spree/core/lib/spree/core/engine.rb`) that exposes only the four built-in buckets:
259
-
260
- * `shipping_methods`
261
- * `tax_rates`
262
- * `promotion_actions_create_adjustments`
263
- * `promotion_actions_create_item_adjustments`
264
-
265
- `Plan.calculators` internally calls `Spree.calculators.send(:plans)` (the tableized model name), so both registration and lookup raise `NoMethodError` until you extend the struct to add a matching `plans` member. Ruby structs cannot gain members at runtime, so this means redefining the `SpreeCalculators` struct (or reassigning `Spree.calculators` to an object that responds to `plans`) before you register anything onto it.
266
-
267
- Once the struct exposes a `plans` member you can register calculators:
268
108
 
269
- **Spree 5.2+:**
109
+ Each calculator declares which settings it takes, so a form can be built for it without knowing the calculator in advance — which is how the dashboard renders these.
270
110
 
271
- ```ruby config/initializers/spree.rb
272
- Rails.application.config.after_initialize do
273
- Spree.calculators.plans << CustomCalculator
274
- end
275
- ```
111
+ > **WARNING:** A calculator with an `amount` also has a **currency**. If it doesn't match the order's currency, the calculator contributes nothing rather than converting. Set up one calculator per currency you sell in, or a promotion will silently do nothing for some customers.
276
112
 
277
- **Spree 5.1 and below:**
113
+ ## Writing your own
278
114
 
279
- ```ruby config/initializers/spree.rb
280
- Rails.application.config.spree.calculators.plans << CustomCalculator
281
- ```
115
+ When none of the built-in calculators expresses your pricing — dimensional weight, a contract rate card, a rule your finance team invented — you can add one. A calculator is a small class with a name and a method that returns an amount, registered so it appears alongside the built-in options.
282
116
 
117
+ For delivery specifically, consider whether you want a calculator or a **delivery rate provider**. A calculator computes a number from what's in the parcel. A provider asks a carrier for real, live rates. If you want what UPS would actually charge today, that's a provider.
283
118
 
284
- Then you can access these calculators by calling this method:
285
-
286
- ```ruby
287
- Plan.calculators
288
- ```
289
-
290
- Using this method, you can then display the calculators as you please. Each object for this new class will need to have a calculator associated so that adjustments can be calculated on them.
291
-
292
- `Spree::CalculatedAdjustments` provides a `has_one :calculator` association (with `accepts_nested_attributes_for` and a presence validation), delegates `compute` to that calculator, exposes a `with_calculator` scope, `calculator_type` / `calculator_type=` accessors, and the `Plan.calculators` registry shown above. To work out what the calculator would compute an amount to be, call `compute` on an instance:
293
-
294
- ```ruby
295
- plan.compute(<calculable object>)
296
- ```
297
-
298
- The module does not define `create_adjustment`, `update_adjustment`, or `compute_amount`. If you also need to build adjustments, include `Spree::AdjustmentSource`, which adds `create_adjustment(order, adjustable, included = false)`. That method calls a `compute_amount` you define on the including model (as `Spree::TaxRate` and the `Spree::Promotion::Actions` classes do). There is no `update_adjustment` method in core.
119
+ See [Custom promotions](../how-to/custom-promotion.md) and [Providers](../providers/overview.md).
299
120
 
300
- ## Related Documentation
121
+ ## Related
301
122
 
302
- - [Adjustments](taxes-discounts-fees.md) — the records calculators compute amounts for.
303
- - [Dependencies](../customization/dependencies.md) — swap out calculators via the Dependencies system.
123
+ - [Fulfillments](fulfillments.md) — delivery methods and rates
124
+ - [Promotions](promotions.md) — the rules that decide when a discount applies
125
+ - [Discounts](discounts.md) — the rows a promotion produces
126
+ - [Taxes](taxes.md) — worked out by providers, not calculators
@@ -136,7 +136,7 @@ This is what lets you design your own checkout. Spree tells you what's missing;
136
136
  shipping_address: {
137
137
  first_name: 'John', last_name: 'Doe',
138
138
  address1: '123 Main St', city: 'Los Angeles',
139
- country_code: 'US', state_abbr: 'CA', postal_code: '90001',
139
+ country_code: 'US', state_code: 'CA', postal_code: '90001',
140
140
  },
141
141
  })
142
142
  ```
@@ -0,0 +1,140 @@
1
+ ---
2
+ title: Catalogs
3
+ description: Showing different products and prices to different audiences — a wholesale range, a channel-specific selection, or one company's negotiated pricing.
4
+ ---
5
+
6
+ ## Overview
7
+
8
+ Not every shopper should see the same store. A wholesale buyer gets trade prices and a range retail never sees. A negotiated account has its own agreed pricing. A retail till carries a subset of what the website does.
9
+
10
+ A catalog answers both halves of that at once: **what an audience sees**, and **what they pay**.
11
+
12
+ ```mermaid
13
+ flowchart LR
14
+ Catalog --> Assortment["Assortment<br/><i>which products</i>"]
15
+ Catalog --> PriceList["Price list<br/><i>what they cost</i>"]
16
+ Catalog --> Audience["Assignments<br/><i>who gets it</i>"]
17
+ ```
18
+
19
+ ```mermaid
20
+ erDiagram
21
+ Catalog ||--o{ CatalogProduct : "assortment"
22
+ Catalog ||--o{ CatalogAssignment : "audiences"
23
+ Catalog }o--o| PriceList : "optional pricing"
24
+ CatalogAssignment }o--|| Channel : "one of"
25
+ CatalogAssignment }o--|| CustomerGroup : "one of"
26
+ CatalogAssignment }o--|| Market : "one of"
27
+ CatalogAssignment }o--|| Company : "one of"
28
+
29
+ Catalog {
30
+ string name
31
+ boolean active
32
+ integer position
33
+ }
34
+ ```
35
+
36
+ ## The two modes
37
+
38
+ The single most useful thing to understand about catalogs is that an **empty assortment means something different from a full one** — it's the switch between the two ways a catalog is used.
39
+
40
+
41
+ - **Pricing overlay** — **Assortment empty.** Nothing is hidden — the audience browses the normal store — but the attached price list applies.
42
+
43
+ This is how "this company sees everything, just at their negotiated prices" is expressed.
44
+ - **Restricted range** — **Assortment has products.** The audience sees *only* what's in it.
45
+
46
+ This is how a wholesale-only range, or a channel-specific selection, is expressed.
47
+
48
+
49
+ That's worth pausing on, because it's the one behaviour that surprises people: adding a product to an empty catalog doesn't add one item to what a customer sees — it switches the catalog into restricting mode, and they now see *only* that item.
50
+
51
+ ## Creating one
52
+
53
+ ```typescript Admin SDK
54
+ const catalog = await adminClient.catalogs.create({
55
+ name: 'Wholesale',
56
+ price_list_id: 'plist_xxx',
57
+ active: true,
58
+ })
59
+ ```
60
+
61
+ The price list is optional. A catalog with an assortment and no price list restricts the range at normal prices; a catalog with a price list and no assortment adjusts prices without hiding anything.
62
+
63
+ ### Filling the assortment
64
+
65
+ ```typescript Admin SDK
66
+ // Add products (a bare array of IDs)
67
+ await adminClient.catalogs.products.create(catalog.id, ['prod_xxx', 'prod_yyy'])
68
+
69
+ const { data: products } = await adminClient.catalogs.products.list(catalog.id)
70
+
71
+ await adminClient.catalogs.products.delete(catalog.id, 'prod_xxx')
72
+ ```
73
+
74
+ Products are positioned, so a catalog also controls the order they're presented in.
75
+
76
+ When a catalog should restrict to exactly what its price list covers, there's a shortcut that copies those products in rather than making someone add them by hand:
77
+
78
+ ```typescript Admin SDK
79
+ const { added_count } = await adminClient.catalogs.importProducts(catalog.id)
80
+ ```
81
+
82
+ ## Choosing the audience
83
+
84
+ A catalog is assigned to one of four things:
85
+
86
+ | Assign to | Meaning |
87
+ |---|---|
88
+ | **[Channel](channels.md)** | Everyone buying through this channel — an app, a retail till |
89
+ | **[Customer group](customers.md)** | A segment — wholesale accounts, staff, a loyalty tier |
90
+ | **[Market](markets.md)** | Everyone in a region |
91
+ | **[Company](companies.md)** | One B2B organization |
92
+
93
+ ```typescript Admin SDK
94
+ await adminClient.catalogs.assign(catalog.id, {
95
+ assignable_type: 'customer_group',
96
+ assignable_id: 'cgrp_xxx',
97
+ })
98
+ ```
99
+
100
+ > **NOTE:** **A company assignment covers the whole subtree.** Assign the group-wide catalog once at the root of a [company tree](companies.md) and every division below inherits it — no re-assigning per branch. A branch can still add its own catalog on top.
101
+
102
+ ## What a shopper ends up seeing
103
+
104
+ When several catalogs could apply, they combine rather than compete:
105
+
106
+ **Step 1: Find the catalogs that apply**
107
+
108
+ For a company buyer, that's the catalogs on their node **and its ancestors**. Otherwise their customer group's catalogs. Otherwise the channel's default catalog, if it has one.
109
+
110
+ **Step 2: Combine the assortments**
111
+
112
+ The shopper sees the union of what those catalogs contain — so a division's extra catalog adds to the group's range rather than replacing it.
113
+
114
+ **Step 3: Any empty assortment lifts the restriction**
115
+
116
+ If one applicable catalog is a pricing overlay, the restriction is off and the shopper sees the full range. An overlay is explicitly "don't hide anything", and that has to win — otherwise adding negotiated pricing would accidentally narrow someone's catalog.
117
+
118
+
119
+ Gated storefront access is checked before any of this — a shopper who has to sign in never reaches catalog resolution.
120
+
121
+ ## How pricing resolves
122
+
123
+ Prices are checked in order, and the first match wins:
124
+
125
+ 1. Price lists attached to the applicable catalogs, **nearest first** — the buyer's own company node before its parent's
126
+ 2. Ordinary price lists whose rules match
127
+ 3. The product's base price
128
+
129
+ Nearest-first is what lets a subsidiary hold a better-negotiated rate than the group's, without disturbing anyone else.
130
+
131
+ > **WARNING:** A price list attached to a catalog applies **because the catalog applies** — its own rules are not consulted, and it's excluded from ordinary rule matching.
132
+ >
133
+ > That exclusion is load-bearing: a price list with no rules would otherwise match everyone, and one company's negotiated pricing would leak to the entire storefront.
134
+
135
+ ## Related
136
+
137
+ - [Companies](companies.md) — B2B buyers and the subtree rule
138
+ - [Pricing](pricing.md) — price lists and rules
139
+ - [Products](products.md) — the catalog being narrowed
140
+ - [Channels](channels.md) — per-channel default catalogs
@@ -3,9 +3,6 @@ title: Channels
3
3
  description: Per-store distribution surfaces — online storefront, POS, marketplace, wholesale — each with its own product catalog and order attribution.
4
4
  ---
5
5
 
6
- import { Since } from '/snippets/since.mdx';
7
-
8
-
9
6
  ## Overview
10
7
 
11
8
  Channels segment a single [Store](stores.md) into distinct selling surfaces. A channel represents *where* an order originates from — the online storefront, an in-person point-of-sale till, a marketplace integration (Amazon, eBay), a B2B wholesale portal, a mobile app — and *which* subset of the store's products is available there.
@@ -93,7 +90,6 @@ This attribution drives reporting (best-selling by channel, revenue per channel)
93
90
 
94
91
  ### Storefront Access Gating
95
92
 
96
-
97
93
  A channel's `storefront_access` decides what an **anonymous** visitor — a request with no authenticated customer — may see. Logged-in customers are never gated. The posture is one of three values:
98
94
 
99
95
  | Mode | Guest sees catalog | Guest sees prices | Use case |