@spree/docs 0.1.44 → 0.1.46
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.
|
@@ -3,6 +3,8 @@ title: Inventory
|
|
|
3
3
|
description: Stock locations, stock items, stock movements, and inventory tracking
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
import { Since } from '/snippets/since.mdx';
|
|
7
|
+
|
|
6
8
|
## Overview
|
|
7
9
|
|
|
8
10
|
Each [Variant](products.md#variants) has a `StockItem` that tracks its inventory at a specific location. A variant can have multiple stock items if it's available at multiple stock locations.
|
|
@@ -11,6 +13,8 @@ When products are sold or returned, individual `InventoryUnit` records track eac
|
|
|
11
13
|
|
|
12
14
|
Adding new inventory to an out-of-stock product that has backorders will first fill the backorders, then update the available count with the remainder.
|
|
13
15
|
|
|
16
|
+
During checkout, Spree holds stock with time-limited [Stock Reservations](#stock-reservations) to prevent two customers from buying the same last unit simultaneously.
|
|
17
|
+
|
|
14
18
|
### Inventory Model Diagram
|
|
15
19
|
|
|
16
20
|
```mermaid
|
|
@@ -52,14 +56,24 @@ erDiagram
|
|
|
52
56
|
integer shipment_id
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
StockReservation {
|
|
60
|
+
integer quantity
|
|
61
|
+
datetime expires_at
|
|
62
|
+
integer stock_item_id
|
|
63
|
+
integer line_item_id
|
|
64
|
+
integer order_id
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
StockLocation ||--o{ StockItem : "has many"
|
|
56
68
|
StockLocation ||--o{ StockTransfer : "source"
|
|
57
69
|
StockLocation ||--o{ StockTransfer : "destination"
|
|
58
70
|
Variant ||--o{ StockItem : "has many"
|
|
59
71
|
Variant ||--o{ InventoryUnit : "has many"
|
|
60
72
|
StockItem ||--o{ StockMovement : "has many"
|
|
73
|
+
StockItem ||--o{ StockReservation : "has many"
|
|
61
74
|
StockTransfer ||--o{ StockMovement : "has many"
|
|
62
75
|
Order ||--o{ InventoryUnit : "has many"
|
|
76
|
+
Order ||--o{ StockReservation : "has many"
|
|
63
77
|
Shipment ||--o{ InventoryUnit : "has many"
|
|
64
78
|
```
|
|
65
79
|
|
|
@@ -69,6 +83,7 @@ erDiagram
|
|
|
69
83
|
- **Stock Movement** → Records changes to Stock Item quantities (purchases, returns, transfers)
|
|
70
84
|
- **Stock Transfer** → Moves inventory between Stock Locations, creating Stock Movements at source and destination
|
|
71
85
|
- **Inventory Unit** → Represents individual units in [Orders](orders.md) and [Shipments](shipments.md)
|
|
86
|
+
- **Stock Reservation** → Time-limited soft hold on a Stock Item during checkout, scoped to a specific Order and Line Item
|
|
72
87
|
|
|
73
88
|
## Inventory Management
|
|
74
89
|
|
|
@@ -151,6 +166,47 @@ Here's the list of attributes for the Stock Movement model:
|
|
|
151
166
|
|
|
152
167
|
Stock Movements are crucial for maintaining accurate inventory levels and for historical tracking of inventory adjustments.
|
|
153
168
|
|
|
169
|
+
## Stock Reservations
|
|
170
|
+
|
|
171
|
+
Stock Reservations are a time-limited soft hold on stock during checkout. When a customer enters checkout, Spree holds the items in their cart for a limited time so other shoppers see reduced availability immediately. Two customers can no longer both pass the availability check on the same last unit only to have one of them fail at order completion.
|
|
172
|
+
|
|
173
|
+
### What changes for the storefront
|
|
174
|
+
|
|
175
|
+
Availability now subtracts the units other customers are holding in active checkouts. Whenever you read whether a variant is in stock — whether for a product page, cart line, or checkout summary — Spree returns the post-reservation number automatically. There's nothing for the storefront to compute and physical stock counts on each `StockItem` are never modified by reservations; reservations are an independent layer that's consulted at read time and cleaned up by background jobs.
|
|
176
|
+
|
|
177
|
+
### Lifecycle
|
|
178
|
+
|
|
179
|
+
| Trigger | Action |
|
|
180
|
+
|---|---|
|
|
181
|
+
| Customer enters checkout | A reservation is created for each line item with an expiry timestamp |
|
|
182
|
+
| Customer continues mutating the cart while in checkout | The expiry is pushed forward |
|
|
183
|
+
| Customer completes the order | The reservation is released; physical stock is decremented as before |
|
|
184
|
+
| Customer empties or abandons the cart | The reservation is released or expires automatically |
|
|
185
|
+
|
|
186
|
+
Reservations attach to each line item; when a line item or order is removed, the reservation goes with it.
|
|
187
|
+
|
|
188
|
+
### Configuration
|
|
189
|
+
|
|
190
|
+
| Setting | Default | Purpose |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `Spree::Config[:stock_reservations_enabled]` | `true` | Global kill switch. When `false`, reservations are not created and availability ignores them — behavior matches pre-5.5. |
|
|
193
|
+
| `Spree::Config[:default_stock_reservation_ttl_minutes]` | `10` | Fallback hold duration when a Store doesn't override. |
|
|
194
|
+
| `store.preferred_stock_reservation_ttl_minutes` | inherits global | Per-Store override. |
|
|
195
|
+
|
|
196
|
+
TTL is a Store-level setting — it's a checkout-experience policy, not a warehouse property. A multi-location cart never has to merge conflicting TTLs from different warehouses.
|
|
197
|
+
|
|
198
|
+
### Insufficient stock during checkout
|
|
199
|
+
|
|
200
|
+
When a cart change in checkout would push the order beyond available stock, the change is rejected up front. The customer sees a validation error immediately, instead of progressing through payment only to fail at the final submit.
|
|
201
|
+
|
|
202
|
+
### Background expiry
|
|
203
|
+
|
|
204
|
+
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.
|
|
205
|
+
|
|
206
|
+
### Backorderable items
|
|
207
|
+
|
|
208
|
+
If a stock item is marked backorderable, it represents unlimited supply, so reservations are skipped entirely for that item. Availability is unaffected.
|
|
209
|
+
|
|
154
210
|
## Inventory Units
|
|
155
211
|
|
|
156
212
|
As we mentioned above, back-ordered, sold, or shipped products are stored as individual `InventoryUnit` objects so they can have relevant information attached to them.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Upgrading to Spree 5.5
|
|
3
|
+
description: This guide covers upgrading a Spree 5.4 application to Spree 5.5.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **INFO:** Before proceeding to upgrade, please ensure you're at [Spree 5.4](5.3-to-5.4.md).
|
|
7
|
+
|
|
8
|
+
## Upgrade steps
|
|
9
|
+
|
|
10
|
+
### Update gems
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bundle update
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Fetch and run missing migrations
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bin/rake spree:install:migrations && bin/rails db:migrate
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Schedule the Stock Reservations expiry job
|
|
23
|
+
|
|
24
|
+
Spree 5.5 introduces time-limited stock reservations during checkout to prevent two customers from buying the same last unit at the same time. Abandoned checkouts leave behind expired reservation rows, and Spree does **not** auto-schedule the cleanup — your application's job runner must run `Spree::StockReservations::ExpireJob` periodically (every minute is the recommended cadence).
|
|
25
|
+
|
|
26
|
+
If you skip this step, expired reservations accumulate in the table indefinitely. The Quantifier still ignores them at availability-check time (so customers see correct stock), but the table grows unbounded.
|
|
27
|
+
|
|
28
|
+
#### sidekiq-cron
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
# config/sidekiq_cron.yml
|
|
32
|
+
expire_stock_reservations:
|
|
33
|
+
cron: "* * * * *"
|
|
34
|
+
class: "Spree::StockReservations::ExpireJob"
|
|
35
|
+
queue: default
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### solid_queue
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
# config/recurring.yml
|
|
42
|
+
expire_stock_reservations:
|
|
43
|
+
schedule: every minute
|
|
44
|
+
class: Spree::StockReservations::ExpireJob
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### good_job
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# config/initializers/good_job.rb
|
|
51
|
+
Rails.application.configure do
|
|
52
|
+
config.good_job.cron = {
|
|
53
|
+
expire_stock_reservations: {
|
|
54
|
+
cron: '* * * * *',
|
|
55
|
+
class: 'Spree::StockReservations::ExpireJob'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### (Optional) Tune the reservation TTL
|
|
62
|
+
|
|
63
|
+
The default reservation TTL is **10 minutes**. To override globally:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
# config/initializers/spree.rb
|
|
67
|
+
Spree::Config[:default_stock_reservation_ttl_minutes] = 15
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
To override per Store, set the preference on the Store record:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
store.update!(preferred_stock_reservation_ttl_minutes: 20)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The per-Store value, when set, takes precedence over the global default.
|
|
77
|
+
|
|
78
|
+
### (Optional) Disable Stock Reservations
|
|
79
|
+
|
|
80
|
+
Stock reservations are enabled by default. To opt out and revert to pre-5.5 behavior (no holds during checkout, Quantifier returns raw `count_on_hand`):
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
# config/initializers/spree.rb
|
|
84
|
+
Spree::Config[:stock_reservations_enabled] = false
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The Quantifier short-circuits before any reservation query when this is `false`, so there's no runtime cost and no table growth.
|
|
88
|
+
|
|
89
|
+
## Behavior changes worth knowing
|
|
90
|
+
|
|
91
|
+
### Cart changes during checkout can now fail with insufficient stock
|
|
92
|
+
|
|
93
|
+
When a customer is in checkout and tries to add an item, increase a quantity, or remove a line item, Spree now re-checks whether the cart still fits in available stock (subtracting what other customers are holding in their own active checkouts). If it doesn't, the change is rejected up front instead of silently completing and failing later at order submission.
|
|
94
|
+
|
|
95
|
+
Storefronts and custom integrations that act on the cart should expect this new failure path and surface the error to the customer.
|
|
96
|
+
|
|
97
|
+
### Storefront availability drops faster under contention
|
|
98
|
+
|
|
99
|
+
Other customers now see availability reduced by all active reservations, not just by completed orders. This is the intended fix to overselling — but if you have a real-time inventory dashboard that reads `count_on_hand` directly (rather than going through Spree's availability checks), you'll want to expose a "Reserved" axis to merchants so they can see in-checkout demand.
|