@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
@@ -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
- # State machine
274
- state_machine initial: :pending do
275
- event :start_mapping do
276
- transition to: :mapping
277
- end
278
- event :complete_mapping do
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](shipments.md)
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](../upgrades/5.4-to-5.5.md#schedule-the-stock-reservations-expiry-job) for sidekiq-cron, solid_queue, and good_job snippets.
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](shipments.md) - How inventory relates to 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](../upgrades/5.4-to-5.5.md) to re-home legacy variant-pinned data when convenient.
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](../upgrades/5.1-to-5.2.md#migrate-to-metafields-or-keep-using-product-properties).
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