@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.
- package/dist/api-reference/store.yaml +48 -48
- package/dist/developer/cli/quickstart.md +1 -1
- package/dist/developer/contributing/creating-an-extension.md +2 -2
- package/dist/developer/core-concepts/addresses.md +3 -3
- package/dist/developer/core-concepts/architecture.md +4 -4
- package/dist/developer/core-concepts/calculators.md +2 -2
- package/dist/developer/core-concepts/carts.md +207 -0
- package/dist/developer/core-concepts/channels.md +3 -3
- package/dist/developer/core-concepts/customers.md +1 -1
- package/dist/developer/core-concepts/events.md +1 -1
- package/dist/developer/core-concepts/fulfillments.md +278 -0
- package/dist/developer/core-concepts/imports-exports.md +6 -18
- package/dist/developer/core-concepts/inventory.md +3 -3
- package/dist/developer/core-concepts/media.md +1 -1
- package/dist/developer/core-concepts/metafields.md +1 -1
- package/dist/developer/core-concepts/orders.md +92 -339
- package/dist/developer/core-concepts/payments.md +1 -1
- package/dist/developer/core-concepts/promotions.md +68 -195
- package/dist/developer/core-concepts/returns-exchanges-claims.md +173 -0
- package/dist/developer/core-concepts/store-credits-gift-cards.md +16 -18
- package/dist/developer/core-concepts/stores.md +1 -1
- package/dist/developer/core-concepts/taxes-discounts-fees.md +199 -0
- package/dist/developer/core-concepts/taxes.md +3 -3
- package/dist/developer/create-spree-app/quickstart.md +1 -1
- package/dist/developer/customization/api.md +36 -11
- package/dist/developer/customization/configuration.md +207 -37
- package/dist/developer/customization/decorators.md +8 -8
- package/dist/developer/customization/permissions.md +34 -229
- package/dist/developer/customization/quickstart.md +159 -108
- package/dist/developer/customization/validations.md +1 -1
- package/dist/developer/dashboard/overview.md +1 -1
- package/dist/developer/how-to/build-a-b2b-store.md +21 -0
- package/dist/developer/how-to/build-a-marketplace.md +25 -0
- package/dist/developer/how-to/custom-api-authentication.md +1 -1
- package/dist/developer/how-to/custom-delivery-rate-provider.md +242 -0
- package/dist/developer/how-to/custom-document-numbers.md +154 -0
- package/dist/developer/how-to/custom-order-routing.md +1 -1
- package/dist/developer/how-to/custom-payment-method.md +1 -1
- package/dist/developer/how-to/custom-promotion.md +76 -115
- package/dist/developer/how-to/custom-report.md +2 -2
- package/dist/developer/how-to/custom-stock-splitter.md +5 -5
- package/dist/developer/how-to/sell-digital-products.md +20 -0
- package/dist/developer/multi-tenant/quickstart.md +1 -1
- package/dist/developer/providers/dam.md +14 -0
- package/dist/developer/providers/erp.md +31 -0
- package/dist/developer/providers/fulfillment.md +25 -0
- package/dist/developer/{deployment/telemetry.md → providers/observability.md} +1 -1
- package/dist/developer/providers/overview.md +44 -0
- package/dist/developer/providers/pim.md +25 -0
- package/dist/developer/providers/sso.md +20 -0
- package/dist/developer/sdk/admin/extending.md +1 -1
- package/dist/developer/sdk/admin/quickstart.md +1 -1
- package/dist/developer/sdk/admin/resources.md +1 -1
- package/dist/developer/sdk/extending.md +1 -1
- package/dist/developer/tutorial/admin-api.md +13 -0
- package/dist/developer/tutorial/dashboard-plugin.md +14 -0
- package/dist/developer/tutorial/events.md +7 -161
- package/dist/developer/tutorial/introduction.md +15 -35
- package/dist/developer/tutorial/model.md +7 -98
- package/dist/developer/tutorial/store-api.md +13 -0
- package/dist/developer/tutorial/storefront.md +12 -0
- package/dist/developer/tutorial/testing.md +7 -711
- package/dist/developer/upgrades/5.6-to-6.0.md +2 -2
- package/package.json +1 -1
- package/dist/developer/admin/admin.md +0 -214
- package/dist/developer/admin/authentication.md +0 -59
- package/dist/developer/admin/components.md +0 -711
- package/dist/developer/admin/custom-css.md +0 -256
- package/dist/developer/admin/custom-javascript.md +0 -116
- package/dist/developer/admin/extending-ui.md +0 -1839
- package/dist/developer/admin/form-builder.md +0 -444
- package/dist/developer/admin/helper-methods.md +0 -531
- package/dist/developer/admin/navigation.md +0 -805
- package/dist/developer/admin/tables.md +0 -490
- package/dist/developer/advanced/adding_spree_to_rails_app.md +0 -92
- package/dist/developer/core-concepts/adjustments.md +0 -113
- package/dist/developer/core-concepts/reports.md +0 -208
- package/dist/developer/core-concepts/shipments.md +0 -307
- package/dist/developer/core-concepts/users.md +0 -303
- package/dist/developer/customization/authentication.md +0 -100
- package/dist/developer/customization/checkout.md +0 -202
- package/dist/developer/customization/emails.md +0 -18
- package/dist/developer/customization/routes.md +0 -24
- package/dist/developer/multi-vendor/installation.md +0 -61
- package/dist/developer/multi-vendor/quickstart.md +0 -17
- package/dist/developer/tutorial/admin.md +0 -206
- package/dist/developer/tutorial/api.md +0 -606
- package/dist/developer/tutorial/extending-models.md +0 -393
- package/dist/developer/tutorial/sdk.md +0 -170
- package/dist/developer/upgrades/2.0-to-2.1.md +0 -46
- package/dist/developer/upgrades/2.1-to-2.2.md +0 -59
- package/dist/developer/upgrades/2.2-to-2.3.md +0 -44
- package/dist/developer/upgrades/2.3-to-2.4.md +0 -42
- package/dist/developer/upgrades/3.0-to-3.1.md +0 -47
- package/dist/developer/upgrades/3.1-to-3.2.md +0 -34
- package/dist/developer/upgrades/3.2-to-3.3.md +0 -70
- package/dist/developer/upgrades/3.3-to-3.4.md +0 -36
- package/dist/developer/upgrades/3.4-to-3.5.md +0 -44
- package/dist/developer/upgrades/3.5-to-3.6.md +0 -40
- package/dist/developer/upgrades/3.6-to-3.7.md +0 -62
- package/dist/developer/upgrades/3.7-to-4.0.md +0 -152
- package/dist/developer/upgrades/4.0-to-4.1.md +0 -92
- package/dist/developer/upgrades/4.1-to-4.2.md +0 -109
- package/dist/developer/upgrades/4.10-to-5.0.md +0 -131
- package/dist/developer/upgrades/4.2-to-4.3.md +0 -100
- package/dist/developer/upgrades/4.3-to-4.4.md +0 -125
- package/dist/developer/upgrades/4.4-to-4.5.md +0 -94
- package/dist/developer/upgrades/4.5-to-4.6.md +0 -119
- package/dist/developer/upgrades/4.6-to-4.7.md +0 -39
- package/dist/developer/upgrades/4.8-to-4.9.md +0 -24
- package/dist/developer/upgrades/4.9-to-4.10.md +0 -24
- package/dist/developer/upgrades/4.x-to-4.8.md +0 -52
- package/dist/developer/upgrades/5.0-to-5.1.md +0 -28
- package/dist/developer/upgrades/5.1-to-5.2.md +0 -131
- package/dist/developer/upgrades/5.2-to-5.3.md +0 -338
- package/dist/developer/upgrades/5.3-to-5.4.md +0 -277
- package/dist/developer/upgrades/5.4-to-5.5.md +0 -301
- package/dist/developer/upgrades/5.5-to-5.6.md +0 -207
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Fulfillments
|
|
3
|
+
description: How Spree models getting items to the customer — fulfillments, delivery methods and zones, and delivery types covering shipping, digital and pickup.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
A fulfillment is one batch of items going to the customer by one method — a parcel from a warehouse, a digital download, or an order waiting at a pickup counter. An order gets one fulfillment per combination of stock location and delivery method, so an order sourced from two warehouses has two.
|
|
9
|
+
|
|
10
|
+
The word is deliberately broader than "shipment". A digital download has no carrier, no address and no package, and click-and-collect ships nowhere at all — a fulfillment covers all of them without dragging along fields that make no sense for most.
|
|
11
|
+
|
|
12
|
+
```mermaid
|
|
13
|
+
erDiagram
|
|
14
|
+
Order ||--o{ Fulfillment : "has many"
|
|
15
|
+
Fulfillment ||--o{ FulfillmentItem : "has many"
|
|
16
|
+
Fulfillment ||--o{ DeliveryRate : "offers"
|
|
17
|
+
Fulfillment }o--|| StockLocation : "sourced from"
|
|
18
|
+
DeliveryRate }o--|| DeliveryMethod : "priced by"
|
|
19
|
+
DeliveryMethod }o--o{ DeliveryZone : "serves"
|
|
20
|
+
|
|
21
|
+
Fulfillment {
|
|
22
|
+
string number
|
|
23
|
+
string status
|
|
24
|
+
string tracking
|
|
25
|
+
string cost
|
|
26
|
+
datetime fulfilled_at
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Fulfillment attributes
|
|
31
|
+
|
|
32
|
+
| Attribute | Description |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `id` | Fulfillment ID, e.g. `ful_86Rf07xd4z` |
|
|
35
|
+
| `number` | Fulfillment number, e.g. `F123456789` |
|
|
36
|
+
| `status` | See [statuses](#statuses) below |
|
|
37
|
+
| `tracking` | Carrier tracking number, once it's on its way |
|
|
38
|
+
| `cost` / `display_cost` | Delivery cost for this fulfillment |
|
|
39
|
+
| `delivery_rates` | The options the customer can choose from |
|
|
40
|
+
| `selected_delivery_rate_id` | The chosen option |
|
|
41
|
+
| `fulfilled_at` | When it went out |
|
|
42
|
+
|
|
43
|
+
## Statuses
|
|
44
|
+
|
|
45
|
+
A fulfillment's status says what **you** did with the parcel — nothing else.
|
|
46
|
+
|
|
47
|
+
| Value | Meaning |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `unfulfilled` | Still with you. Nothing has left |
|
|
50
|
+
| `fulfilled` | It went out — handed to the carrier, or waiting at the counter for a pickup order |
|
|
51
|
+
| `delivered` | The customer has it |
|
|
52
|
+
| `canceled` | It won't be sent |
|
|
53
|
+
|
|
54
|
+
Statuses only move forward: once something has gone out it stays gone out, and a later payment problem changes the order's [payment status](orders.md#statuses) rather than the fulfillment.
|
|
55
|
+
|
|
56
|
+
Whether a fulfillment *can* go out — the order is paid, nothing is on backorder — is checked when you fulfill it, and the answer comes back with a reason. It is not baked into the status, so a refund never moves a parcel backwards. Staff who need to ship against an invoice can pass `force`.
|
|
57
|
+
|
|
58
|
+
### Where the parcel actually is
|
|
59
|
+
|
|
60
|
+
Carrier reports live on a second field, `tracking_status`, and never change `status`:
|
|
61
|
+
|
|
62
|
+
| Value | Meaning |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `pre_transit` | Label made, carrier hasn't got it yet |
|
|
65
|
+
| `in_transit` | On its way |
|
|
66
|
+
| `out_for_delivery` | On the van |
|
|
67
|
+
| `available_for_pickup` | Waiting at a carrier location |
|
|
68
|
+
| `delivered` | Carrier says it arrived |
|
|
69
|
+
| `return_to_sender` | Coming back to you |
|
|
70
|
+
| `failure` | Delivery failed — a bad address, damage, a refused parcel |
|
|
71
|
+
| `unknown` | The carrier said something we don't recognise |
|
|
72
|
+
|
|
73
|
+
Keeping the two apart is the point. A parcel that bounces still shows `fulfilled`, because you did hand it over; the trouble shows up as `return_to_sender` and you decide what to do about it. The one report that crosses over is delivery, which also sets `delivered` and `delivered_at`.
|
|
74
|
+
|
|
75
|
+
`delivered_at` is what return windows and the EU withdrawal period count from, so it records when the carrier says the parcel arrived rather than when you heard about it.
|
|
76
|
+
|
|
77
|
+
Carriers set this through their provider's webhook. With no carrier integration, staff mark receipt by hand — `markDelivered` in the Admin SDK — which is also how pickup orders get closed out.
|
|
78
|
+
|
|
79
|
+
## Delivery types
|
|
80
|
+
|
|
81
|
+
Every delivery method has a type that decides how it behaves:
|
|
82
|
+
|
|
83
|
+
| Type | Behaviour |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `shipping` | Physical delivery to an address. Someone marks it sent. |
|
|
86
|
+
| `digital` | Available the moment the order is placed. No address needed. |
|
|
87
|
+
| `pickup` | Collection from one of your own stock locations. |
|
|
88
|
+
| `pickup_point` | Collection from a third-party point — a locker or partner shop. |
|
|
89
|
+
|
|
90
|
+
This is what lets a store sell a downloadable album and a vinyl record in the same order without any special handling in your storefront: the download is ready immediately, the record gets a tracking number.
|
|
91
|
+
|
|
92
|
+
## Choosing delivery at checkout
|
|
93
|
+
|
|
94
|
+
Each fulfillment on a cart offers delivery rates. The customer picks one per fulfillment.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
```typescript Store SDK
|
|
98
|
+
const cart = await client.carts.get(cartId)
|
|
99
|
+
|
|
100
|
+
cart.fulfillments[0].delivery_rates
|
|
101
|
+
// => [{ id: 'rate_xxx', name: 'DHL Express', display_cost: '$12.00',
|
|
102
|
+
// estimated_delivery_date: '2026-08-07' }, ...]
|
|
103
|
+
|
|
104
|
+
await client.carts.fulfillments.update(cartId, cart.fulfillments[0].id, {
|
|
105
|
+
selected_delivery_rate_id: 'rate_xxx',
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash cURL
|
|
110
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/fulfillments/ful_xxx' \
|
|
111
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
112
|
+
-H 'X-Spree-Token: abc123' \
|
|
113
|
+
-H 'Content-Type: application/json' \
|
|
114
|
+
-d '{ "selected_delivery_rate_id": "rate_xxx" }'
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
Rates carry more than a price — carrier, service level and estimated delivery date — so you can show "DHL Express, arrives Tuesday" instead of just a number.
|
|
119
|
+
|
|
120
|
+
## Pickup
|
|
121
|
+
|
|
122
|
+
For pickup methods, ask the API where the customer can collect:
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
```typescript Store SDK
|
|
126
|
+
// Your own pickup-enabled locations
|
|
127
|
+
const locations = await client.deliveryMethods.pickupLocations('dm_xxx')
|
|
128
|
+
|
|
129
|
+
// Third-party pickup points near the customer
|
|
130
|
+
const points = await client.deliveryMethods.pickupPoints('dm_xxx', {
|
|
131
|
+
latitude: 34.0522,
|
|
132
|
+
longitude: -118.2437,
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```bash cURL
|
|
137
|
+
curl 'https://api.mystore.com/api/v3/store/delivery_methods/dm_xxx/pickup_locations' \
|
|
138
|
+
-H 'X-Spree-API-Key: pk_xxx'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
## Managing fulfillments
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
```typescript Admin SDK
|
|
146
|
+
// What's still with you
|
|
147
|
+
const fulfillments = await adminClient.orders.fulfillments.list('or_xxx')
|
|
148
|
+
|
|
149
|
+
// Add tracking, then mark it sent
|
|
150
|
+
await adminClient.orders.fulfillments.update('or_xxx', 'ful_xxx', {
|
|
151
|
+
tracking: '1Z999AA10123456784',
|
|
152
|
+
})
|
|
153
|
+
await adminClient.orders.fulfillments.fulfill('or_xxx', 'ful_xxx')
|
|
154
|
+
|
|
155
|
+
// Ship only some of it — the chosen units split off and ship,
|
|
156
|
+
// the rest stays open
|
|
157
|
+
await adminClient.orders.fulfillments.fulfill('or_xxx', 'ful_xxx', {
|
|
158
|
+
items: [{ item_id: 'li_xxx', quantity: 1 }],
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Ship against an unpaid invoice — your call, not the API's
|
|
162
|
+
await adminClient.orders.fulfillments.fulfill('or_xxx', 'ful_xxx', { force: true })
|
|
163
|
+
|
|
164
|
+
// Record receipt yourself when no carrier reports it for you
|
|
165
|
+
await adminClient.orders.fulfillments.markDelivered('or_xxx', 'ful_xxx')
|
|
166
|
+
|
|
167
|
+
// Split it when only part can go now
|
|
168
|
+
await adminClient.orders.fulfillments.split('or_xxx', 'ful_xxx', {
|
|
169
|
+
items: [{ fulfillment_item_id: 'fi_xxx', quantity: 1 }],
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// Cancel
|
|
173
|
+
await adminClient.orders.fulfillments.cancel('or_xxx', 'ful_xxx')
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```bash cURL
|
|
177
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/fulfillments/ful_xxx' \
|
|
178
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
179
|
+
-H 'Content-Type: application/json' \
|
|
180
|
+
-d '{ "tracking": "1Z999AA10123456784" }'
|
|
181
|
+
|
|
182
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/fulfillments/ful_xxx/fulfill' \
|
|
183
|
+
-H 'X-Spree-API-Key: sk_xxx'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
## Delivery methods and zones
|
|
188
|
+
|
|
189
|
+
A **delivery method** is what the customer picks — "Standard", "Express", "Collect in store". A **delivery zone** decides where it's available.
|
|
190
|
+
|
|
191
|
+
Zones match on country and state, and also on postal code prefixes and ranges, so you can offer same-day delivery to a handful of city postcodes without listing every one:
|
|
192
|
+
|
|
193
|
+
```typescript Admin SDK
|
|
194
|
+
await adminClient.deliveryZones.create({
|
|
195
|
+
name: 'London same-day',
|
|
196
|
+
members: [
|
|
197
|
+
{ member_type: 'postal_code', country_code: 'GB', postal_code_prefix: 'SW1' },
|
|
198
|
+
{ member_type: 'postal_code', country_code: 'GB', postal_code_from: 'EC1A', postal_code_to: 'EC1V' },
|
|
199
|
+
],
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
A zone member is either a whole country, a state, or a postal code prefix or range.
|
|
204
|
+
|
|
205
|
+
> **NOTE:** Delivery zones cover delivery only. Tax is handled separately — see [Taxes](taxes.md).
|
|
206
|
+
|
|
207
|
+
### Pricing and rules
|
|
208
|
+
|
|
209
|
+
Delivery is priced either by a rule you configure in the dashboard — flat rate, per item, price bands — or by asking a carrier for live rates.
|
|
210
|
+
|
|
211
|
+
Methods can also carry conditions that decide when they're offered at all: free shipping over a certain order value, or heavy items excluded from letter post. These are applied in one place, so both kinds of pricing respect them.
|
|
212
|
+
|
|
213
|
+
## Adding your own statuses
|
|
214
|
+
|
|
215
|
+
A made-to-order business — print on demand, furniture built after purchase —
|
|
216
|
+
has a stage between "order placed" and "handed to the carrier". There are two
|
|
217
|
+
ways to model it, and the lighter one is usually right.
|
|
218
|
+
|
|
219
|
+
**Track production in your own model, gate handover with a hook.** A production
|
|
220
|
+
pipeline usually has more resolution than one word — queued, printing, quality
|
|
221
|
+
check, packed — and that detail belongs in your own tables or your
|
|
222
|
+
integration's metadata, not in the parcel's lifecycle vocabulary. What core
|
|
223
|
+
needs to know is only *whether the parcel may go out yet*, and that is exactly
|
|
224
|
+
what the fulfill workflow's validate hook expresses:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
Spree.hooks.register('fulfillments.fulfill.validate') do |flow|
|
|
228
|
+
work_order = MyApp::WorkOrder.find_by(fulfillment: flow.fulfillment)
|
|
229
|
+
flow.reject!('still in production') unless work_order&.completed?
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The fulfillment stays `unfulfilled` while you build the thing; your storefront
|
|
234
|
+
renders "we're making your furniture" as presentation, the same way pickup
|
|
235
|
+
orders render `fulfilled` as "ready for pickup".
|
|
236
|
+
|
|
237
|
+
**Add a real status when merchants need to act on the stage.** If staff filter
|
|
238
|
+
by it, reports group by it, or webhooks fire on entering it, make it a
|
|
239
|
+
first-class status:
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
# an initializer
|
|
243
|
+
Spree::Fulfillment.add_status('in_production', after: 'unfulfilled')
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
That gives you the `in_production?` predicate, the `.in_production` scope and
|
|
247
|
+
a valid value. Moving into it is deliberately not declarative — write a small
|
|
248
|
+
workflow, which is also where the printer submission or the workshop handoff
|
|
249
|
+
belongs:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
class MyApp::Fulfillments::StartProduction < Spree::Workflow
|
|
253
|
+
def perform(fulfillment:)
|
|
254
|
+
super
|
|
255
|
+
step :ensure_startable # refuse unless fulfillment.unfulfilled?
|
|
256
|
+
step :mark_in_production # update!(status: 'in_production') + publish an event
|
|
257
|
+
external_step :submit_to_printer
|
|
258
|
+
success(fulfillment.reload)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Core's own actions keep working on your status without overrides: the guards
|
|
264
|
+
ask whether the parcel has *already gone out*, not whether it holds one of the
|
|
265
|
+
built-in values, so an `in_production` fulfillment can still be fulfilled or
|
|
266
|
+
canceled. Statuses are additive only — core workflows guard on core statuses,
|
|
267
|
+
so removing one would silently break them — and confirmed receipt still only
|
|
268
|
+
follows handover.
|
|
269
|
+
|
|
270
|
+
## Events
|
|
271
|
+
|
|
272
|
+
Fulfillments publish [events](events.md) — `fulfillment.created`, `fulfillment.fulfilled`, `fulfillment.delivered`, `fulfillment.canceled` — which also reach [webhooks](webhooks.md). Use them to notify a customer, push to a warehouse system, or send tracking emails.
|
|
273
|
+
|
|
274
|
+
## Related
|
|
275
|
+
|
|
276
|
+
- [Orders](orders.md) — how delivery status rolls up
|
|
277
|
+
- [Inventory](inventory.md) — stock locations and availability
|
|
278
|
+
- [Returns, Exchanges & Claims](returns-exchanges-claims.md) — items coming back
|
|
@@ -270,24 +270,12 @@ module Spree
|
|
|
270
270
|
has_many :mappings # Field mappings
|
|
271
271
|
has_many :rows # CSV rows to process
|
|
272
272
|
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
transition from: :mapping, to: :completed_mapping
|
|
280
|
-
end
|
|
281
|
-
event :start_processing do
|
|
282
|
-
transition from: :completed_mapping, to: :processing
|
|
283
|
-
end
|
|
284
|
-
event :complete do
|
|
285
|
-
transition from: :processing, to: :completed
|
|
286
|
-
end
|
|
287
|
-
event :fail do
|
|
288
|
-
transition to: :failed
|
|
289
|
-
end
|
|
290
|
-
end
|
|
273
|
+
# Statuses. Each move between them is a workflow — Spree::Imports::
|
|
274
|
+
# StartMapping, CompleteMapping, StartProcessing, Complete and
|
|
275
|
+
# RetryFailedRows — so every step of the pipeline can be hooked.
|
|
276
|
+
include Spree::HasStatus
|
|
277
|
+
has_status :pending, :mapping, :completed_mapping, :processing, :completed, :failed,
|
|
278
|
+
default: :pending
|
|
291
279
|
|
|
292
280
|
# Key methods
|
|
293
281
|
def import_schema # Returns schema class instance
|
|
@@ -82,7 +82,7 @@ erDiagram
|
|
|
82
82
|
- **Stock Item** → Tracks quantity (`count_on_hand`) for a specific Variant at a specific Stock Location
|
|
83
83
|
- **Stock Movement** → Records changes to Stock Item quantities (purchases, returns, transfers)
|
|
84
84
|
- **Stock Transfer** → Moves inventory between Stock Locations, creating Stock Movements at source and destination
|
|
85
|
-
- **Inventory Unit** → Represents individual units in [Orders](orders.md) and [Shipments](
|
|
85
|
+
- **Inventory Unit** → Represents individual units in [Orders](orders.md) and [Shipments](fulfillments.md)
|
|
86
86
|
- **Stock Reservation** → Time-limited soft hold on a Stock Item during checkout, scoped to a specific Order and Line Item
|
|
87
87
|
|
|
88
88
|
## Inventory Management
|
|
@@ -276,7 +276,7 @@ When a cart change in checkout would push the order beyond available stock, the
|
|
|
276
276
|
|
|
277
277
|
### Background expiry
|
|
278
278
|
|
|
279
|
-
Abandoned checkouts leave behind expired reservation rows. Spree provides a job to clean them up but does **not** auto-schedule it — your application's job runner needs to invoke it periodically (every minute is typical). See the [5.4 to 5.5 upgrade guide](
|
|
279
|
+
Abandoned checkouts leave behind expired reservation rows. Spree provides a job to clean them up but does **not** auto-schedule it — your application's job runner needs to invoke it periodically (every minute is typical). See the [5.4 to 5.5 upgrade guide](/v5/developer/upgrades/5.4-to-5.5#schedule-the-stock-reservations-expiry-job) for sidekiq-cron, solid_queue, and good_job snippets.
|
|
280
280
|
|
|
281
281
|
### Backorderable items
|
|
282
282
|
|
|
@@ -319,7 +319,7 @@ If you don't need to track inventory, you can disable it:
|
|
|
319
319
|
## Related Documentation
|
|
320
320
|
|
|
321
321
|
- [Products](products.md) - Product and variant management
|
|
322
|
-
- [Shipments](
|
|
322
|
+
- [Shipments](fulfillments.md) - How inventory relates to shipments
|
|
323
323
|
- [Orders](orders.md) - How inventory is allocated to orders
|
|
324
324
|
- [Admin SDK resources](../sdk/admin/resources.md) - `stockLocations`, `stockItems`, and `stockTransfers` methods used in the examples above
|
|
325
325
|
- [Admin API authentication](../../api-reference/admin-api/authentication.md) - How to obtain and scope the secret key (`sk_xxx`) used by these calls
|
|
@@ -104,7 +104,7 @@ Variants belonging to a different product are silently dropped — the API rejec
|
|
|
104
104
|
|
|
105
105
|
The Store API's `media` field on a product returns its gallery — product-level media when present, falling back to legacy variant-pinned images during the transition. On a variant, `media` returns the assets linked to that variant via `variant_ids`, falling back to direct variant uploads.
|
|
106
106
|
|
|
107
|
-
This dual rendering means existing storefronts keep working during the upgrade; new uploads attach to the product, and you opt into a [one-shot migration](
|
|
107
|
+
This dual rendering means existing storefronts keep working during the upgrade; new uploads attach to the product, and you opt into a [one-shot migration](/v5/developer/upgrades/5.4-to-5.5) to re-home legacy variant-pinned data when convenient.
|
|
108
108
|
|
|
109
109
|
### Video
|
|
110
110
|
|
|
@@ -280,7 +280,7 @@ Spree has two permanent, complementary systems for custom data — **metadata fo
|
|
|
280
280
|
|
|
281
281
|
**Use [Metadata](../customization/metadata.md)** for external system IDs, tracking attribution, syncing with integrations, or simple write-and-forget data that only backend systems need to read.
|
|
282
282
|
|
|
283
|
-
> **WARNING:** Product Properties are deprecated and will be removed in Spree 6.0. For new projects, always use Metafields. For existing projects, plan to migrate using the [migration guide](
|
|
283
|
+
> **WARNING:** Product Properties are deprecated and will be removed in Spree 6.0. For new projects, always use Metafields. For existing projects, plan to migrate using the [migration guide](/v5/developer/upgrades/5.1-to-5.2#migrate-to-metafields-or-keep-using-product-properties).
|
|
284
284
|
|
|
285
285
|
## Related Documentation
|
|
286
286
|
|