@spree/docs 0.1.223 → 0.1.224

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.
@@ -17,6 +17,13 @@ erDiagram
17
17
  StockLevel ||--o{ StockReservation : "held by checkouts"
18
18
  Order ||--o{ Fulfillment : "ships as"
19
19
  Fulfillment ||--o{ FulfillmentItem : "contains"
20
+ Supplier ||--o{ PurchaseOrder : "supplies"
21
+ PurchaseOrder ||--o{ PurchaseOrderItem : "orders"
22
+ StockTransfer ||--o{ StockTransferItem : "moves"
23
+ PurchaseOrder ||--o{ StockReceipt : "received as"
24
+ StockTransfer ||--o{ StockReceipt : "received as"
25
+ StockReceipt ||--o{ StockReceiptItem : "counts"
26
+ StockReceipt ||--o{ StockMovement : "lands"
20
27
 
21
28
  StockLevel {
22
29
  integer count_on_hand
@@ -53,10 +60,11 @@ A stock location is somewhere stock physically sits: a warehouse, a shop, a thir
53
60
  | `name` | What staff call it |
54
61
  | `active` | Whether it can be used for new orders |
55
62
  | `default` | Used first when nothing else decides |
63
+ | `kind` | What sort of place it is — `warehouse`, `store` or `fulfillment_center` |
56
64
  | `country_code`, `state_code`, `city`, `postal_code` | Where it is — used for shipping rates and for finding the nearest one |
57
65
  | `backorderable_default` | Whether new stock levels here allow backorders |
58
- | `fulfillable` | Whether orders can ship from here, or it's display-only stock |
59
- | `pickup` | Whether customers can collect here |
66
+ | `pickup_enabled` | Whether customers can collect here |
67
+ | `returns_enabled` | Whether customers can bring returns here |
60
68
 
61
69
  ```typescript Admin SDK
62
70
  const { data: locations } = await adminClient.stockLocations.list()
@@ -77,11 +85,11 @@ A stock level is the intersection of a variant and a location. They're created f
77
85
  ```typescript Admin SDK
78
86
  // Stock for one location
79
87
  const { data: levels } = await adminClient.stockLevels.list({
80
- filter: { stock_location_id_eq: 'sl_xxx' },
88
+ stock_location_id_eq: 'sloc_xxx',
81
89
  })
82
90
 
83
91
  // Correct a single count
84
- await adminClient.stockLevels.update('slv_xxx', {
92
+ await adminClient.stockLevels.update('sl_xxx', {
85
93
  count_on_hand: 150,
86
94
  backorderable: true,
87
95
  })
@@ -89,15 +97,15 @@ await adminClient.stockLevels.update('slv_xxx', {
89
97
  // Set many at once — the right call for a nightly sync
90
98
  await adminClient.stockLevels.bulkUpsert({
91
99
  stock_levels: [
92
- { variant_id: 'var_xxx', stock_location_id: 'sl_xxx', count_on_hand: 40 },
93
- { variant_id: 'var_yyy', stock_location_id: 'sl_xxx', count_on_hand: 12 },
100
+ { variant_id: 'variant_xxx', stock_location_id: 'sloc_xxx', count_on_hand: 40 },
101
+ { variant_id: 'variant_yyy', stock_location_id: 'sloc_xxx', count_on_hand: 12 },
94
102
  ],
95
103
  })
96
104
  ```
97
105
 
98
106
  ```bash CLI
99
- spree api get '/stock_levels?q[stock_location_id_eq]=sl_xxx'
100
- spree api patch /stock_levels/slv_xxx -d '{"count_on_hand": 150}'
107
+ spree api get '/stock_levels?q[stock_location_id_eq]=sloc_xxx'
108
+ spree api patch /stock_levels/sl_xxx -d '{"count_on_hand": 150}'
101
109
  ```
102
110
 
103
111
 
@@ -116,12 +124,15 @@ Every change to stock is recorded as a movement, so "why is this number what it
116
124
  | `adjusted` | A manual correction, such as after a stock count |
117
125
 
118
126
  ```typescript Admin SDK
127
+ // Everything that happened to one SKU, across every location
119
128
  const { data: movements } = await adminClient.stockMovements.list({
120
- filter: { variant_id_eq: 'var_xxx' },
129
+ stock_level_variant_id_eq: 'variant_xxx',
121
130
  })
122
131
  ```
123
132
 
124
- Each movement points at what caused it — the order, the fulfillment, the transfer — so an audit trail reads as a sequence of business events rather than a list of numbers that changed.
133
+ Each movement points at what caused it — the order, the fulfillment, the transfer, the purchase order, and for stock that arrived, the delivery it came in on — so an audit trail reads as a sequence of business events rather than a list of numbers that changed.
134
+
135
+ A movement that arrived through a purchase order also carries what the units cost, so "what did this stock cost us" is answerable from the ledger rather than reconstructed from the orders that bought it.
125
136
 
126
137
  ## What a shopper sees
127
138
 
@@ -165,10 +176,100 @@ When new stock arrives, backorders are filled first, and only the remainder beco
165
176
 
166
177
  ## Moving stock between locations
167
178
 
168
- A stock transfer moves inventory from one location to another, and reflects that this takes time: stock is booked out of the source, is in transit, and is received at the destination — where a partial receipt is a normal outcome, because sometimes not everything arrives.
179
+ A stock transfer moves inventory from one location to another, and reflects that this takes time: stock is booked out of the source, is in transit, and is counted in at the destination — where a partial delivery is a normal outcome, because sometimes not everything arrives at once.
180
+
181
+ That is four steps, not one. A transfer is planned as a draft, packed, sent, and finally counted in:
182
+
183
+ ```typescript Admin SDK
184
+ const transfer = await adminClient.stockTransfers.create({
185
+ source_location_id: 'sloc_xxx',
186
+ destination_location_id: 'sloc_yyy',
187
+ items: [{ variant_id: 'variant_xxx', quantity_shipped: 10 }],
188
+ })
189
+
190
+ // The units leave the source here — and are on no shelf until they arrive.
191
+ await adminClient.stockTransfers.markInTransit(transfer.id)
192
+
193
+ // Eight arrived intact and two were crushed. See "Counting a delivery in".
194
+ await adminClient.stockTransfers.stockReceipts.create(transfer.id, {
195
+ items: [{ id: 'sti_xxx', quantity_accepted: 8, quantity_rejected: 2, rejection_reason: 'damaged' }],
196
+ })
197
+ ```
198
+
199
+ | Status | What it means |
200
+ |---|---|
201
+ | `draft` | A plan. Lines can still change; nothing has moved |
202
+ | `ready_to_ship` | Packed. Lines are frozen, nothing has left yet — `markDraft` unfreezes it |
203
+ | `in_transit` | The units have left the source and are on no shelf |
204
+ | `partially_received` | Some units have been counted in; the rest are still on the road |
205
+ | `received` | Everything that left has arrived — or the balance was closed short |
206
+ | `over_received` | More arrived than was sent |
207
+ | `canceled` | Called off. Once units have left, the merchant says whether they come back or are written off |
208
+
209
+ Two things follow from the middle step. Availability at the destination does not rise until somebody counts the goods in, which is what makes the number trustworthy. And cancelling a transfer whose units have already left is a decision rather than an undo: either they come back to the source, or they are written off as lost.
210
+
211
+ ## Buying stock in
212
+
213
+ Stock that comes from a supplier is a purchase order, not a transfer — it has a supplier, a cost per unit and a date it was promised for, none of which apply to moving goods you already own.
214
+
215
+ ```typescript Admin SDK
216
+ const order = await adminClient.purchaseOrders.create({
217
+ supplier_id: 'sup_xxx',
218
+ destination_location_id: 'sloc_xxx',
219
+ expected_at: '2026-10-01',
220
+ cancel_by: '2026-10-15',
221
+ items: [{ variant_id: 'variant_xxx', quantity_ordered: 100, unit_cost: '12.50' }],
222
+ })
223
+
224
+ await adminClient.purchaseOrders.markOrdered(order.id)
225
+
226
+ // A delivery arrives: 58 accepted, 2 refused. The order stays open for the rest.
227
+ await adminClient.purchaseOrders.stockReceipts.create(order.id, {
228
+ reference: 'DN-4471',
229
+ items: [{ id: 'poi_xxx', quantity_accepted: 58, quantity_rejected: 2, rejection_reason: 'damaged' }],
230
+ })
231
+ ```
232
+
233
+ | Status | What it means |
234
+ |---|---|
235
+ | `draft` | Being put together. Lines and costs can still change |
236
+ | `ordered` | Sent to the supplier. Lines are frozen — `markDraft` reopens it while nothing has arrived |
237
+ | `partially_received` | Some units have been counted in; the supplier still owes the rest |
238
+ | `received` | Every line has what it expected — or the balance was closed short |
239
+ | `over_received` | The supplier sent more than was ordered |
240
+ | `canceled` | Called off. Units already received stay on the shelf |
241
+
242
+ Ordered stock is never available stock. Nothing reaches `count_on_hand` until a delivery is counted in, because a merchant who has ordered goods does not have them — and suppliers under-ship often enough that receiving in parts is the ordinary case.
243
+
244
+ An order carries the day the supplier promised (`expected_at`) and, optionally, the day after which the goods are no longer wanted (`cancel_by`). Both are calendar dates. The dashboard lists filter on *Overdue* and *Past cancel-by*; nothing cancels automatically, because that is a call to confirm with the supplier.
245
+
246
+ Purchase orders export to CSV, one row per line, which is the file a merchant sends a supplier. They import from one too: rows sharing a `reference` become one draft order, naming the supplier, warehouse and SKU by name — a name that does not exist fails the row rather than creating anything.
247
+
248
+ Suppliers themselves are an address book, kept per store:
249
+
250
+ ```typescript Admin SDK
251
+ const { data: suppliers } = await adminClient.suppliers.list()
252
+ ```
253
+
254
+ ## Counting a delivery in
255
+
256
+ Transfers and purchase orders are received the same way: each delivery is a **stock receipt**, numbered and dated, carrying the packing-slip reference and — per line — what was accepted and what was refused, with a reason.
257
+
258
+ ```typescript Admin SDK
259
+ const { data: deliveries } = await adminClient.purchaseOrders.stockReceipts.list(order.id, {
260
+ expand: ['items'],
261
+ })
262
+ ```
263
+
264
+ A receipt records *this delivery's* counts, not running totals: a second box adds to the first rather than restating it. Accepted units land on the destination's shelf, each as a `received` movement that names the receipt. Refused units are recorded and never stocked; the reason is one of `damaged`, `wrong_item`, `expired` or `other`. Omit `items` to count in everything still outstanding, intact.
265
+
266
+ > **NOTE:** **Refused units count differently on the two documents.** A supplier's refused units are still owed — the goods go back, and the order stays `partially_received` until they are replaced. A transfer's refused units have *arrived*, in whatever state: a crushed unit is not still in the van, so it counts toward the trip being over.
267
+
268
+ When the balance is not coming — the supplier is out of stock, the missing units fell off the van — the document is **closed short**: it settles in `received` with `closed_short_at` and the reason recorded, and the outstanding count stays on each line as the record of the gap. Nothing moves.
169
269
 
170
270
  ```typescript Admin SDK
171
- const { data: transfers } = await adminClient.stockTransfers.list()
271
+ await adminClient.purchaseOrders.close(order.id, { reason: 'Supplier out of stock' })
272
+ await adminClient.stockTransfers.close(transfer.id, { reason: 'Two fell off the van' })
172
273
  ```
173
274
 
174
275
  ## Turning tracking off
@@ -71,7 +71,9 @@ await client.customers.addresses.create('cus_xxx', {
71
71
  |---|---|
72
72
  | `client.stockLocations` | CRUD on stock locations. |
73
73
  | `client.stockItems` | List, get, update, delete stock items. |
74
- | `client.stockTransfers` | List, get, create, delete stock transfers. |
74
+ | `client.stockTransfers` | CRUD plus the lifecycle: `markReady`, `markInTransit`, `receive`, `cancel`. |
75
+ | `client.suppliers` | CRUD on the suppliers you buy from. |
76
+ | `client.purchaseOrders` | CRUD plus `markOrdered`, `receive`, `cancel`. |
75
77
 
76
78
  ## Sales channels
77
79
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.223",
3
+ "version": "0.1.224",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",