@spree/docs 0.1.176 → 0.1.178

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 (118) hide show
  1. package/dist/api-reference/store.yaml +48 -48
  2. package/dist/developer/cli/quickstart.md +1 -1
  3. package/dist/developer/contributing/creating-an-extension.md +2 -2
  4. package/dist/developer/core-concepts/addresses.md +3 -3
  5. package/dist/developer/core-concepts/architecture.md +4 -4
  6. package/dist/developer/core-concepts/calculators.md +2 -2
  7. package/dist/developer/core-concepts/carts.md +207 -0
  8. package/dist/developer/core-concepts/channels.md +3 -3
  9. package/dist/developer/core-concepts/customers.md +1 -1
  10. package/dist/developer/core-concepts/events.md +1 -1
  11. package/dist/developer/core-concepts/fulfillments.md +278 -0
  12. package/dist/developer/core-concepts/imports-exports.md +6 -18
  13. package/dist/developer/core-concepts/inventory.md +3 -3
  14. package/dist/developer/core-concepts/media.md +1 -1
  15. package/dist/developer/core-concepts/metafields.md +1 -1
  16. package/dist/developer/core-concepts/orders.md +92 -339
  17. package/dist/developer/core-concepts/payments.md +1 -1
  18. package/dist/developer/core-concepts/promotions.md +68 -195
  19. package/dist/developer/core-concepts/returns-exchanges-claims.md +173 -0
  20. package/dist/developer/core-concepts/store-credits-gift-cards.md +16 -18
  21. package/dist/developer/core-concepts/stores.md +1 -1
  22. package/dist/developer/core-concepts/taxes-discounts-fees.md +199 -0
  23. package/dist/developer/core-concepts/taxes.md +3 -3
  24. package/dist/developer/create-spree-app/quickstart.md +1 -1
  25. package/dist/developer/customization/api.md +36 -11
  26. package/dist/developer/customization/configuration.md +207 -37
  27. package/dist/developer/customization/decorators.md +8 -8
  28. package/dist/developer/customization/permissions.md +34 -229
  29. package/dist/developer/customization/quickstart.md +159 -108
  30. package/dist/developer/customization/validations.md +1 -1
  31. package/dist/developer/dashboard/overview.md +1 -1
  32. package/dist/developer/how-to/build-a-b2b-store.md +21 -0
  33. package/dist/developer/how-to/build-a-marketplace.md +25 -0
  34. package/dist/developer/how-to/custom-api-authentication.md +1 -1
  35. package/dist/developer/how-to/custom-delivery-rate-provider.md +242 -0
  36. package/dist/developer/how-to/custom-document-numbers.md +154 -0
  37. package/dist/developer/how-to/custom-order-routing.md +1 -1
  38. package/dist/developer/how-to/custom-payment-method.md +1 -1
  39. package/dist/developer/how-to/custom-promotion.md +76 -115
  40. package/dist/developer/how-to/custom-report.md +2 -2
  41. package/dist/developer/how-to/custom-stock-splitter.md +5 -5
  42. package/dist/developer/how-to/sell-digital-products.md +20 -0
  43. package/dist/developer/multi-tenant/quickstart.md +1 -1
  44. package/dist/developer/providers/dam.md +14 -0
  45. package/dist/developer/providers/erp.md +31 -0
  46. package/dist/developer/providers/fulfillment.md +25 -0
  47. package/dist/developer/{deployment/telemetry.md → providers/observability.md} +1 -1
  48. package/dist/developer/providers/overview.md +44 -0
  49. package/dist/developer/providers/pim.md +25 -0
  50. package/dist/developer/providers/sso.md +20 -0
  51. package/dist/developer/sdk/admin/extending.md +1 -1
  52. package/dist/developer/sdk/admin/quickstart.md +1 -1
  53. package/dist/developer/sdk/admin/resources.md +1 -1
  54. package/dist/developer/sdk/extending.md +1 -1
  55. package/dist/developer/tutorial/admin-api.md +13 -0
  56. package/dist/developer/tutorial/dashboard-plugin.md +14 -0
  57. package/dist/developer/tutorial/events.md +7 -161
  58. package/dist/developer/tutorial/introduction.md +15 -35
  59. package/dist/developer/tutorial/model.md +7 -98
  60. package/dist/developer/tutorial/store-api.md +13 -0
  61. package/dist/developer/tutorial/storefront.md +12 -0
  62. package/dist/developer/tutorial/testing.md +7 -711
  63. package/dist/developer/upgrades/5.6-to-6.0.md +2 -2
  64. package/package.json +1 -1
  65. package/dist/developer/admin/admin.md +0 -214
  66. package/dist/developer/admin/authentication.md +0 -59
  67. package/dist/developer/admin/components.md +0 -711
  68. package/dist/developer/admin/custom-css.md +0 -256
  69. package/dist/developer/admin/custom-javascript.md +0 -116
  70. package/dist/developer/admin/extending-ui.md +0 -1839
  71. package/dist/developer/admin/form-builder.md +0 -444
  72. package/dist/developer/admin/helper-methods.md +0 -531
  73. package/dist/developer/admin/navigation.md +0 -805
  74. package/dist/developer/admin/tables.md +0 -490
  75. package/dist/developer/advanced/adding_spree_to_rails_app.md +0 -92
  76. package/dist/developer/core-concepts/adjustments.md +0 -113
  77. package/dist/developer/core-concepts/reports.md +0 -208
  78. package/dist/developer/core-concepts/shipments.md +0 -307
  79. package/dist/developer/core-concepts/users.md +0 -303
  80. package/dist/developer/customization/authentication.md +0 -100
  81. package/dist/developer/customization/checkout.md +0 -202
  82. package/dist/developer/customization/emails.md +0 -18
  83. package/dist/developer/customization/routes.md +0 -24
  84. package/dist/developer/multi-vendor/installation.md +0 -61
  85. package/dist/developer/multi-vendor/quickstart.md +0 -17
  86. package/dist/developer/tutorial/admin.md +0 -206
  87. package/dist/developer/tutorial/api.md +0 -606
  88. package/dist/developer/tutorial/extending-models.md +0 -393
  89. package/dist/developer/tutorial/sdk.md +0 -170
  90. package/dist/developer/upgrades/2.0-to-2.1.md +0 -46
  91. package/dist/developer/upgrades/2.1-to-2.2.md +0 -59
  92. package/dist/developer/upgrades/2.2-to-2.3.md +0 -44
  93. package/dist/developer/upgrades/2.3-to-2.4.md +0 -42
  94. package/dist/developer/upgrades/3.0-to-3.1.md +0 -47
  95. package/dist/developer/upgrades/3.1-to-3.2.md +0 -34
  96. package/dist/developer/upgrades/3.2-to-3.3.md +0 -70
  97. package/dist/developer/upgrades/3.3-to-3.4.md +0 -36
  98. package/dist/developer/upgrades/3.4-to-3.5.md +0 -44
  99. package/dist/developer/upgrades/3.5-to-3.6.md +0 -40
  100. package/dist/developer/upgrades/3.6-to-3.7.md +0 -62
  101. package/dist/developer/upgrades/3.7-to-4.0.md +0 -152
  102. package/dist/developer/upgrades/4.0-to-4.1.md +0 -92
  103. package/dist/developer/upgrades/4.1-to-4.2.md +0 -109
  104. package/dist/developer/upgrades/4.10-to-5.0.md +0 -131
  105. package/dist/developer/upgrades/4.2-to-4.3.md +0 -100
  106. package/dist/developer/upgrades/4.3-to-4.4.md +0 -125
  107. package/dist/developer/upgrades/4.4-to-4.5.md +0 -94
  108. package/dist/developer/upgrades/4.5-to-4.6.md +0 -119
  109. package/dist/developer/upgrades/4.6-to-4.7.md +0 -39
  110. package/dist/developer/upgrades/4.8-to-4.9.md +0 -24
  111. package/dist/developer/upgrades/4.9-to-4.10.md +0 -24
  112. package/dist/developer/upgrades/4.x-to-4.8.md +0 -52
  113. package/dist/developer/upgrades/5.0-to-5.1.md +0 -28
  114. package/dist/developer/upgrades/5.1-to-5.2.md +0 -131
  115. package/dist/developer/upgrades/5.2-to-5.3.md +0 -338
  116. package/dist/developer/upgrades/5.3-to-5.4.md +0 -277
  117. package/dist/developer/upgrades/5.4-to-5.5.md +0 -301
  118. package/dist/developer/upgrades/5.5-to-5.6.md +0 -207
@@ -1,208 +0,0 @@
1
- ---
2
- title: Reports
3
- ---
4
-
5
- ## Overview
6
-
7
- Spree provides a flexible reporting system that allows you to generate data exports for sales analytics, product performance, and custom business metrics. Reports are generated asynchronously and delivered as downloadable CSV files.
8
-
9
- ### Report System Diagram
10
-
11
- ```mermaid
12
- erDiagram
13
- Report {
14
- string type
15
- string currency
16
- datetime date_from
17
- datetime date_to
18
- bigint store_id
19
- bigint user_id
20
- }
21
-
22
- ReportLineItem {
23
- string record
24
- string report
25
- }
26
-
27
- Store {
28
- string name
29
- string code
30
- }
31
-
32
- AdminUser {
33
- string email
34
- }
35
-
36
- GenerateJob {
37
- string report_id
38
- }
39
-
40
- Report ||--|| Store : "belongs to"
41
- Report ||--o| AdminUser : "belongs to"
42
- Report ||--o{ ReportLineItem : "generates"
43
- Report ||--|| GenerateJob : "triggers"
44
- ```
45
-
46
- **Key relationships:**
47
- - **Report** is the base model for all report types using Single Table Inheritance (STI)
48
- - **ReportLineItem** transforms raw database records into formatted report rows
49
- - Reports are scoped to a **Store** and optionally track the **AdminUser** who created them
50
- - **GenerateJob** handles asynchronous CSV generation via the events system
51
-
52
- ## Architecture
53
-
54
- The reporting system uses several design patterns:
55
-
56
- 1. **Single Table Inheritance (STI)**: All report types inherit from `Spree::Report` and are stored in the same database table with a `type` column
57
- 2. **Presenter Pattern**: `Spree::ReportLineItem` subclasses transform raw records into formatted output
58
- 3. **Event-Driven Processing**: Report generation is triggered asynchronously via Spree's events system
59
- 4. **Registry Pattern**: Reports are registered in `Spree.reports` for discovery and validation
60
-
61
- ## Built-in Reports
62
-
63
- Spree ships with two built-in reports:
64
-
65
- ### Sales Total Report
66
-
67
- The Sales Total report provides line-item level detail for all completed orders within a date range.
68
-
69
- **Columns:**
70
- - `date` - Order completion date
71
- - `order` - Order number
72
- - `product` - Variant descriptive name
73
- - `quantity` - Line item quantity
74
- - `pre_tax_amount` - Amount before taxes
75
- - `promo_total` - Promotion discounts applied
76
- - `shipment_total` - Shipping costs
77
- - `tax_total` - Tax amount
78
- - `total` - Final amount including all adjustments
79
-
80
- ### Products Performance Report
81
-
82
- The Products Performance report aggregates sales metrics by product for the specified period.
83
-
84
- **Columns:**
85
- - `sku` - Product SKU
86
- - `name` - Product name
87
- - `vendor` - Vendor name (if multi-vendor enabled)
88
- - `brand` - Brand name
89
- - `category_lvl0/1/2` - Category hierarchy from main taxon
90
- - `price` - Current product price
91
- - `weeks_online` - Weeks since product became available
92
- - `pre_tax_amount` - Total pre-tax sales
93
- - `tax_total` - Total taxes collected
94
- - `quantity` - Total units sold
95
- - `promo_total` - Total promotion discounts
96
- - `total` - Total revenue
97
-
98
- ## How Reports Work Internally
99
-
100
- ### Generation Flow
101
-
102
- 1. **User creates report** via admin UI with date range, currency, and optional vendor
103
- 2. **Report is saved** to the database
104
- 3. **`report.created` event fires** via `publishes_lifecycle_events` concern
105
- 4. **`Spree::ReportSubscriber` catches event** and enqueues `GenerateJob`
106
- 5. **Background job runs `report.generate`**:
107
- - Iterates through `line_items_scope` in batches
108
- - Transforms each record via `ReportLineItem`
109
- - Writes CSV to temp file
110
- - Attaches CSV to report via ActiveStorage
111
- - Sends notification email to user
112
-
113
- ### Key Files
114
-
115
- | File | Purpose |
116
- |------|---------|
117
- | `core/app/models/spree/report.rb` | Base report model |
118
- | `core/app/models/spree/report_line_item.rb` | Base line item presenter |
119
- | `core/app/models/spree/reports/*.rb` | Built-in report implementations |
120
- | `core/app/models/spree/report_line_items/*.rb` | Built-in line item formatters |
121
- | `core/app/subscribers/spree/report_subscriber.rb` | Event subscriber for async generation |
122
- | `core/app/jobs/spree/reports/generate_job.rb` | Background job for CSV creation |
123
- | `core/app/mailers/spree/report_mailer.rb` | Notification emails |
124
- | `admin/app/controllers/spree/admin/reports_controller.rb` | Admin UI controller |
125
-
126
- ### Report Base Class Methods
127
-
128
- ```ruby
129
- # Returns the scope of records for the report
130
- def line_items_scope
131
- raise NotImplementedError
132
- end
133
-
134
- # Returns formatted line items (with optional limit for preview)
135
- def line_items(options = {})
136
- scope = line_items_scope
137
- scope = scope.limit(options[:limit]) if options[:limit].present?
138
- scope.map { |record| line_item_class.new(record: record, report: self) }
139
- end
140
-
141
- # Generates the CSV file and handles attachment
142
- def generate
143
- generate_csv
144
- handle_attachment
145
- send_report_done_email
146
- end
147
-
148
- # Returns the corresponding line item class
149
- def line_item_class
150
- "Spree::ReportLineItems::#{type.demodulize}".safe_constantize
151
- end
152
- ```
153
-
154
- ### ReportLineItem Base Class Methods
155
-
156
- ```ruby
157
- # Returns column headers for display
158
- def self.headers
159
- attribute_types.keys.map do |attribute|
160
- { name: attribute.to_sym, label: Spree.t(attribute.to_sym) }
161
- end
162
- end
163
-
164
- # Returns column names for CSV header row
165
- def self.csv_headers
166
- attribute_types.keys
167
- end
168
-
169
- # Converts line item to CSV row array
170
- def to_csv
171
- self.class.attribute_types.keys.map { |attr| send(attr) }
172
- end
173
- ```
174
-
175
- ## Configuration
176
-
177
- ### Report Preview Limit
178
-
179
- Configure the number of preview rows shown in the admin UI:
180
-
181
- ```ruby config/initializers/spree.rb
182
- Spree::Admin::RuntimeConfig.reports_line_items_limit = 100
183
- ```
184
-
185
- ### Background Job Queue
186
-
187
- By default, report jobs run on the `:default` queue. To route them to a dedicated queue, configure your job processor:
188
-
189
- ```ruby
190
- # ActiveJob example
191
- Spree.queues.reports = :reports
192
- ```
193
-
194
- ## Permissions
195
-
196
- Report access is controlled by CanCanCan. By default, only users with the `:manage` ability on `Spree::Report` can access reports. Configure permissions in your permission sets.
197
-
198
- ```ruby
199
- # Example: Allow all admins to view reports
200
- can :manage, Spree::Report
201
- ```
202
-
203
- ## Related Documentation
204
-
205
- - [Build a Custom Report](../how-to/custom-report.md) - Step-by-step guide to creating custom reports
206
- - [Events](events.md) - How report generation uses the events system
207
- - [Imports & Exports](imports-exports.md) - The broader async-export, ActiveStorage-attachment pattern reports share
208
- - [Admin API Endpoints](../../api-reference/admin-api/endpoints.md) - Endpoints for creating and downloading reports from an integration
@@ -1,307 +0,0 @@
1
- ---
2
- title: Shipments
3
- description: How Spree models shipments, shipping methods, calculators, stock locations, split shipments, and the fulfillment workflow from order to delivery.
4
- ---
5
-
6
- import { Since } from '/snippets/since.mdx';
7
-
8
- ## Overview
9
-
10
- A shipment represents a package being sent to a customer from a [Stock Location](inventory.md#stock-locations). Each order can have one or more shipments — Spree automatically splits orders into multiple shipments when items need to ship from different locations or require different shipping methods.
11
-
12
- ```mermaid
13
- erDiagram
14
- Order ||--o{ Shipment : "has many"
15
- Shipment ||--o{ ShippingRate : "has many"
16
- Shipment ||--o{ InventoryUnit : "has many"
17
- Shipment }o--|| StockLocation : "ships from"
18
- Shipment }o--|| ShippingMethod : "selected method"
19
- ShippingMethod ||--o{ ShippingRate : "has many"
20
- ShippingMethod }o--o{ Zone : "serves"
21
- ShippingMethod }o--o{ ShippingCategory : "handles"
22
- ShippingMethod }o--o{ Store : "available in"
23
- Product }o--|| ShippingCategory : "belongs to"
24
-
25
- Shipment {
26
- string number
27
- string tracking
28
- string state
29
- decimal cost
30
- datetime shipped_at
31
- }
32
-
33
- ShippingMethod {
34
- string name
35
- string tracking_url
36
- integer position
37
- }
38
-
39
- ShippingRate {
40
- decimal cost
41
- boolean selected
42
- }
43
- ```
44
-
45
- **Key relationships:**
46
- - **Shipment** tracks delivery of items from a [Stock Location](inventory.md#stock-locations)
47
- - **Shipping Method** defines the carrier/service (UPS, FedEx, etc.)
48
- - **Shipping Rate** represents the calculated cost for a method
49
- - **[Zone](addresses.md#zones)** defines geographic regions for shipping availability
50
- - **Shipping Category** groups products with similar shipping requirements
51
-
52
- ## Shipment Attributes
53
-
54
- The Store API/SDK exposes shipments as `fulfillments` with these attributes:
55
-
56
- | Attribute | Description | Example |
57
- |-----------|-------------|---------|
58
- | `number` | Unique fulfillment identifier | `H12345678901` |
59
- | `tracking` | Carrier tracking number | `1Z999AA10123456784` |
60
- | `status` | Current fulfillment status | `shipped` |
61
- | `fulfillment_type` | `shipping` or `digital` | `shipping` |
62
- | `cost` | Delivery cost | `9.99` |
63
- | `fulfilled_at` | When the fulfillment was shipped | `2025-07-21T14:36:00Z` |
64
- | `stock_location` | Where items ship from | `{ id: "sloc_xxx", name: "Warehouse NYC" }` |
65
- | `delivery_method` | The selected delivery method | `{ id: "dm_xxx", name: "UPS Ground" }` |
66
- | `delivery_rates` | Available rates for the customer to pick from | `[{ id: "dr_xxx", cost: "9.99", selected: true, ... }]` |
67
-
68
- ## Shipment States
69
-
70
- **Step 1: pending**
71
-
72
- The shipment has backordered inventory or the order is not yet paid.
73
-
74
- **Step 2: ready**
75
-
76
- All items are in stock and the order is paid. Ready to ship.
77
-
78
- **Step 3: shipped**
79
-
80
- The shipment is on its way to the customer.
81
-
82
- **Step 4: canceled**
83
-
84
- The shipment was canceled. All items are restocked.
85
-
86
-
87
- ## Selecting Shipping Rates
88
-
89
- During checkout, after the customer provides a shipping address, Spree calculates available shipping rates for each shipment. The customer must select a rate before proceeding. See the [Cart & Checkout SDK](../sdk/store/cart-checkout.md) guide for the full storefront cart and fulfillment flow.
90
-
91
-
92
- ```typescript Store SDK
93
- // Get the cart with its fulfillments and available delivery rates
94
- // (the Store API/SDK exposes shipments as `fulfillments`)
95
- const cart = await client.carts.get(cartId)
96
-
97
- // Each fulfillment has available delivery rates
98
- cart.fulfillments?.forEach(fulfillment => {
99
- console.log(fulfillment.number) // "H12345678901"
100
- console.log(fulfillment.delivery_rates) // [{ id: "dr_xxx", name: "UPS Ground", cost: "9.99", selected: true }, ...]
101
- })
102
-
103
- // Select a delivery rate
104
- await client.carts.fulfillments.update(cartId, fulfillment.id, {
105
- selected_delivery_rate_id: 'dr_xxx',
106
- })
107
- ```
108
-
109
- ```typescript Admin SDK
110
- const order = await adminClient.orders.get(orderId)
111
- ```
112
-
113
- ```bash cURL
114
- # Get fulfillments
115
- curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx?expand=fulfillments' \
116
- -H 'X-Spree-API-Key: pk_xxx' \
117
- -H 'X-Spree-Token: abc123'
118
-
119
- # Select a delivery rate
120
- curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/fulfillments/ful_xxx' \
121
- -H 'X-Spree-API-Key: pk_xxx' \
122
- -H 'X-Spree-Token: abc123' \
123
- -H 'Content-Type: application/json' \
124
- -d '{ "selected_delivery_rate_id": "dr_xxx" }'
125
- ```
126
-
127
-
128
- ## Shipping Methods
129
-
130
- Shipping methods represent the carrier services available to customers (e.g., UPS Ground, FedEx Overnight, DHL International). Each shipping method is scoped to:
131
-
132
- - **[Zones](addresses.md#zones)** — geographic regions where the method is available
133
- - **Shipping Categories** — product groups the method handles
134
- - **[Stores](stores.md)** — which stores offer this method
135
-
136
- Only methods whose zone matches the customer's shipping address are offered at checkout.
137
-
138
- ### Shipping Categories
139
-
140
- Shipping categories group products with similar shipping requirements. For example:
141
-
142
- - **Light** — lightweight items like stickers
143
- - **Regular** — standard products
144
- - **Heavy** — items over a certain weight
145
- - **Oversized** — large items requiring special handling
146
-
147
- Each product is assigned a shipping category. Shipping methods can be restricted to handle only certain categories, and the shipping cost calculator uses the category to determine pricing.
148
-
149
- ### Calculators
150
-
151
- Each shipping method uses a [Calculator](calculators.md) to determine the cost. Spree includes these built-in calculators:
152
-
153
- | Calculator | Description |
154
- |------------|-------------|
155
- | Flat rate per order | Same cost regardless of items |
156
- | Flat rate per item | Fixed cost per item |
157
- | Flat percent | Percentage of the order total |
158
- | Flexible rate | One rate for the first item, another for each additional |
159
- | Price sack | Tiered pricing based on order total |
160
-
161
- You can create custom calculators for more complex pricing. See the [Calculators guide](calculators.md).
162
-
163
- ## Order Routing
164
-
165
- When an order moves from cart to checkout, Spree decides which [Stock Location](inventory.md#stock-locations) fulfills it. **Order Routing** is the system that makes that decision — driven by configurable rules so merchants can express preferences like "fulfill from the customer's preferred warehouse first," "minimize the number of split shipments," or "always pick the closest location."
166
-
167
- ```mermaid
168
- flowchart LR
169
- Order[Order entering checkout] --> Strategy
170
- Strategy[Routing strategy] --> Rules[(Rules,<br/>per channel)]
171
- Strategy --> Locations[(Eligible<br/>stock locations)]
172
- Rules --> Reducer
173
- Locations --> Reducer
174
- Reducer[Reducer:<br/>full ranking,<br/>best-first] --> Ordered[Locations<br/>in rank order]
175
- Ordered --> Shipments[Shipments created<br/>top location first]
176
- ```
177
-
178
- ### How the decision is made
179
-
180
- Each Channel (the distribution surface — online storefront, POS, wholesale portal) has an ordered list of **routing rules**. When the customer enters checkout, Spree walks the rules from highest priority to lowest and asks each rule to rank the candidate locations. The result is a full best-to-worst ordering of every eligible location. The top-ranked location packs as much of the cart as it can; anything it can't cover spills over to the next-ranked location, and so on.
181
-
182
- The default rules every channel ships with:
183
-
184
- | Order | Rule | What it does |
185
- |---|---|---|
186
- | 1 | **Preferred Location** | If the order has a preferred location set (e.g. by an admin staff member), that location ranks first. Otherwise abstains. |
187
- | 2 | **Minimize Splits** | Prefers locations that can fulfill the most line items single-handedly. The location that covers the most cart on its own ranks higher. |
188
- | 3 | **Default Location** | Tie-breaker: ranks the store default first, then other active locations. Always ranks every candidate so there's always a complete order. |
189
-
190
- Reorder them, deactivate them, or add new rules without touching code — they're just rows in `spree_order_routing_rules`.
191
-
192
- ### Channels
193
-
194
- Every order belongs to a Channel. Routing rules are scoped to the channel, so the wholesale channel can have completely different fulfillment logic from the online storefront. New channels seed their own three default rules automatically.
195
-
196
- A channel can also override the routing **strategy** entirely — useful when one channel needs an algorithmic shape that's different from rules-walking, e.g. a POS channel that always picks the brick-and-mortar location, or a wholesale channel that delegates routing to an external warehouse management system.
197
-
198
- ### When it fires
199
-
200
- Routing fires once, when the order transitions into the `delivery` step of checkout (from `address`). The `cart`→`address` transition only assigns default addresses; routing runs on the next step. It produces one or more `Shipment`s, each tied to a chosen stock location. The decision is sticky — once the shipments are created, their locations stay fixed unless the merchant edits them in the admin or the cart is cleared and re-routed.
201
-
202
- Routing happens **after** stock reservations: by the time routing runs, the cart has already reserved the units it needs. Reservations and routing make their decisions at different layers — reservations protect the variant's total inventory across all locations, routing picks which location ships. They coexist correctly today, with a small inefficiency around location-pinning that's planned to be tightened in 6.0.
203
-
204
- ### Extending routing
205
-
206
- For business-specific logic — proximity to the shipping address, customer-tier-aware fulfillment, refrigerated SKUs, day-of-week dispatch — you write a custom **rule** that plugs into the existing pipeline. For replacing the algorithm entirely (OMS delegation, ML-based routing, multi-order optimization solvers), you write a custom **strategy**.
207
-
208
- See the [Build Custom Order Routing](../how-to/custom-order-routing.md) guide for both.
209
-
210
- ## Split Shipments
211
-
212
- An order's allocation can split along two independent axes:
213
-
214
- | Axis | Decided by | Question it answers |
215
- |---|---|---|
216
- | **Across stock locations** | [Order Routing](#order-routing) | _Which_ locations fulfill this order? |
217
- | **Within a stock location** | [Stock Splitters](#stock-splitters) | _How_ do we break each location's allocation into separate packages? |
218
-
219
- The two layers compose cleanly — routing picks and ranks the locations, then each chosen location is independently broken down by the splitter chain. The Prioritizer then walks all the resulting packages in rank order and decides which package fulfills each inventory unit.
220
-
221
- ```mermaid
222
- flowchart TB
223
- A[Order with 3 items] --> R[Routing strategy]
224
- R --> NYC[NYC Warehouse<br/>covers 2 items]
225
- R --> LA[LA Warehouse<br/>covers 1 item]
226
- NYC --> P1[Packer + splitters]
227
- LA --> P2[Packer + splitters]
228
- P1 --> S1["Shipment 1: Light items<br/>UPS Ground from NYC"]
229
- P1 --> S2["Shipment 2: Heavy items<br/>FedEx Freight from NYC"]
230
- P2 --> S3["Shipment 3: Backup item<br/>UPS Ground from LA"]
231
- ```
232
-
233
- ### How Splitting Works
234
-
235
- 1. **Order Routing** produces a ranked list of stock locations (best first).
236
- 2. **Per-location packing**: each location's units pass through `Spree::Stock::Packer` plus the configured splitter chain (`Spree.stock_splitters`), producing one or more packages per location — broken out by shipping category, on-hand vs backorder, digital vs physical, and so on.
237
- 3. **Prioritizer**: walks all resulting packages in rank order and assigns each inventory unit to the first package that has it on hand. Units the top-ranked location can't cover spill into lower-ranked location packages; unfilled units are flagged backordered.
238
- 4. **Shipment creation**: each remaining package becomes a `Shipment`. The customer selects a shipping rate for each shipment independently.
239
-
240
- ## Stock Splitters
241
-
242
- Splitters are the per-location half of the split-shipment story. Each splitter takes the packages produced so far for one location and decides whether to break them further along its own axis. Splitters are chained — every splitter's output feeds into the next.
243
-
244
- Splitters never see more than one location's allocation at a time, so they cannot overlap with routing's location decision. Routing answers "which locations?"; splitters answer "how do we slice each location's packages?"
245
-
246
- ### Built-in Splitters
247
-
248
- | Splitter | Default? | What it does |
249
- |---|---|---|
250
- | `Spree::Stock::Splitter::ShippingCategory` | Yes | Groups items in each package by their product's [Shipping Category](#shipping-categories), so each package has only one category. Ensures shipping methods scoped to specific categories receive the right items. |
251
- | `Spree::Stock::Splitter::Backordered` | Yes | Splits each package into an on-hand part and a backordered part. The two halves can ship at different times with different ETAs. |
252
- | `Spree::Stock::Splitter::Digital` | Yes | Separates digital items from physical items so digital deliveries don't get bundled with a physical package. |
253
- | `Spree::Stock::Splitter::Weight` | Opt-in | Caps each package at a weight threshold (default `150`). Splits heavy packages until each is under the limit. Used by carriers with per-package weight limits. |
254
-
255
- The default chain is set in `Spree::Core::Engine` and can be overridden:
256
-
257
- ```ruby
258
- # config/initializers/spree.rb
259
- Rails.application.config.spree.stock_splitters = [
260
- Spree::Stock::Splitter::ShippingCategory,
261
- Spree::Stock::Splitter::Backordered,
262
- Spree::Stock::Splitter::Digital,
263
- Spree::Stock::Splitter::Weight # add the opt-in weight cap
264
- ]
265
- ```
266
-
267
- The order matters — each splitter's output is the next one's input.
268
-
269
- ### Extending Splitters
270
-
271
- To add custom splitting logic — refrigerated SKUs, gift wrap separation, bulky-vs-small bin separation, anything that needs to fan one location's allocation into multiple shipments — write a new subclass of `Spree::Stock::Splitter::Base`. See the [Build Custom Stock Splitter](../how-to/custom-stock-splitter.md) guide.
272
-
273
- ## Examples
274
-
275
- ### Simple Setup
276
-
277
- A store selling T-shirts to the US and Europe with 2 carriers:
278
-
279
- | Method | Zone | Pricing |
280
- |--------|------|---------|
281
- | USPS Ground | US | $5 first item + $2 each additional |
282
- | FedEx | EU | $10 per item |
283
-
284
- This requires:
285
- - 1 shipping category (default)
286
- - 1 stock location
287
- - 2 shipping methods with appropriate zones and calculators
288
-
289
- ### Advanced Setup
290
-
291
- A store shipping from 2 locations (New York, Los Angeles) with 3 carriers and 3 shipping categories:
292
-
293
- | Category / Method | DHL | FedEx | USPS |
294
- |:-|:-|:-|:-|
295
- | Light | $5/item | $10 flat | $8/item |
296
- | Regular | $5/item | $2/item | $8/item |
297
- | Heavy | $50/item | $20 + $15/add'l | $20/item |
298
-
299
- ## Related Documentation
300
-
301
- - [Orders](orders.md) — Checkout flow and shipping rate selection
302
- - [Inventory](inventory.md) — Stock locations and inventory management
303
- - [Calculators](calculators.md) — Shipping rate calculators
304
- - [Addresses](addresses.md) — Shipping address and zones
305
- - [Build Custom Order Routing](../how-to/custom-order-routing.md) — Custom rules and strategies for choosing the fulfillment location
306
- - [Events](events.md) — Subscribe to shipment events (e.g., `shipment.shipped`)
307
- - [Admin API](../../api-reference/admin-api/introduction.md) — Inspect and manage shipments, shipping methods, and categories from the back office