@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.
- package/dist/developer/core-concepts/addresses.md +106 -198
- package/dist/developer/core-concepts/architecture.md +97 -126
- package/dist/developer/core-concepts/calculators.md +75 -252
- package/dist/developer/core-concepts/carts.md +1 -1
- package/dist/developer/core-concepts/catalogs.md +140 -0
- package/dist/developer/core-concepts/channels.md +0 -4
- package/dist/developer/core-concepts/commissions.md +253 -0
- package/dist/developer/core-concepts/companies.md +240 -0
- package/dist/developer/core-concepts/customers.md +0 -3
- package/dist/developer/core-concepts/discounts.md +133 -0
- package/dist/developer/core-concepts/events.md +83 -576
- package/dist/developer/core-concepts/fees.md +144 -0
- package/dist/developer/core-concepts/imports-exports.md +105 -679
- package/dist/developer/core-concepts/inventory.md +114 -248
- package/dist/developer/core-concepts/markets.md +9 -12
- package/dist/developer/core-concepts/media.md +127 -16
- package/dist/developer/core-concepts/metafields.md +123 -200
- package/dist/developer/core-concepts/order-totals.md +110 -0
- package/dist/developer/core-concepts/orders.md +1 -1
- package/dist/developer/core-concepts/payments.md +11 -14
- package/dist/developer/core-concepts/pricing.md +11 -13
- package/dist/developer/core-concepts/products.md +191 -62
- package/dist/developer/core-concepts/promotions.md +12 -11
- package/dist/developer/core-concepts/search-filtering.md +2 -4
- package/dist/developer/core-concepts/sellers.md +210 -0
- package/dist/developer/core-concepts/staff-roles.md +56 -23
- package/dist/developer/core-concepts/store-credits-gift-cards.md +0 -3
- package/dist/developer/core-concepts/taxes.md +125 -113
- package/dist/developer/core-concepts/translations.md +61 -68
- package/dist/developer/core-concepts/webhooks.md +25 -59
- package/dist/developer/how-to/custom-promotion.md +3 -3
- package/package.json +1 -1
- package/dist/developer/core-concepts/companies-and-catalogs.md +0 -81
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
+
- **[Delivery methods](fulfillments.md)** — what delivery costs
|
|
15
|
+
- **[Promotions](promotions.md)** — how big a discount is
|
|
116
16
|
|
|
117
|
-
|
|
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
|
-
|
|
151
|
-
* `apply_only_on_full_priced_items`: When enabled, skips line items that are already on sale.
|
|
19
|
+
## Choosing a calculator
|
|
152
20
|
|
|
153
|
-
|
|
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
|
|
23
|
+
### For delivery charges
|
|
156
24
|
|
|
157
|
-
|
|
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
|
-
|
|
34
|
+
### For promotion discounts
|
|
160
35
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
45
|
+
## Worked examples
|
|
167
46
|
|
|
168
|
-
|
|
47
|
+
<details>
|
|
48
|
+
<summary>Free delivery over $50, otherwise $7</summary>
|
|
169
49
|
|
|
170
|
-
|
|
50
|
+
**Price sack**, with `minimal_amount: 50`, `discount_amount: 0`, `normal_amount: 7`.
|
|
171
51
|
|
|
172
|
-
|
|
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
|
-
|
|
179
|
-
# Returns the value after performing the required calculation
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
```
|
|
54
|
+
</details>
|
|
183
55
|
|
|
184
|
-
|
|
56
|
+
<details>
|
|
57
|
+
<summary>$3 to ship the first item, $1 for each extra</summary>
|
|
185
58
|
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
+
</details>
|
|
199
64
|
|
|
200
|
-
|
|
65
|
+
<details>
|
|
66
|
+
<summary>10% off the order</summary>
|
|
201
67
|
|
|
202
|
-
|
|
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
|
-
|
|
70
|
+
A $31 order gets **$3.10** off.
|
|
211
71
|
|
|
212
|
-
|
|
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
|
-
|
|
77
|
+
**Tiered percent** — 5% over $100, 10% over $250, 15% over $500.
|
|
220
78
|
|
|
221
|
-
|
|
79
|
+
The order total picks the tier. One promotion covers the whole ladder instead of three competing ones.
|
|
222
80
|
|
|
223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
+
```typescript Admin SDK
|
|
90
|
+
// What can price a delivery method?
|
|
91
|
+
const calculators = await adminClient.deliveryMethods.calculators()
|
|
239
92
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
## Writing your own
|
|
278
114
|
|
|
279
|
-
|
|
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
|
-
|
|
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
|
|
121
|
+
## Related
|
|
301
122
|
|
|
302
|
-
- [
|
|
303
|
-
- [
|
|
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',
|
|
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 |
|