Skip to content

Reading stock

Most integrations that get stock wrong do not get the write wrong. They re-derive a figure the module already publishes, in a slightly different way, and then disagree with the admin about it.

Everything here is a read. p2lab_stockly.viewer or the entity’s own :read privilege is enough.

GET warehouse/stock/product/{productId}?page=1&limit=10 ACL: p2lab_stockly_warehouse_stock:read · at most 100 rows per page

One row per (warehouse, bin), plus the reorder rules and the purchasable figure that belong to the whole warehouse rather than to the page.

{
"data": [
{
"id": "0191f0…",
"productId": "0191f3…",
"warehouseId": "0191f1…",
"warehouseName": "Main warehouse",
"binLocationId": "0191f2…",
"binLocationCode": "A-01-02",
"binRole": null,
"stock": 24,
"reserved": 6,
"available": 18
}
],
"rules": {},
"purchasable": {},
"total": 3,
"page": 1,
"limit": 10
}

binRole: null is an ordinary storage bin. Any other value is a buffer; see the stock model for what each one excludes.

GET warehouse/stock/product/{productId}/summary?withVariants=1 ACL: p2lab_stockly_warehouse_stock:read

The figures behind the product page’s tiles. withVariants=1 makes a parent aggregate its variants.

{
"data": {
"total": 24, "reserved": 6, "available": 17, "backordered": 1,
"pending": 0, "warehouses": 2, "expiring": 0,
"inTransit": 0, "onOrder": 40
}
}
FieldMeans
totalthe raw physical count, buffers included
reservedhow much orders have claimed
availablewhat can still be sold
backordereddemand no shelf covers
pendingunits in the receiving buffer or in transit — on their way in
expiringlots with an expiry within 30 days
inTransit, onOrderowed by another warehouse / by a supplier

GET warehouse/{warehouseId}/product/{productId}/available ACL: p2lab_stockly_warehouse_stock:read

Answers {"available": 12} and nothing else. Use it for a scan check or a gate, where loading the rows and summing them locally is extra work for the same answer.

GET warehouse/stock/product/{productId}/reservations?grain=order&page=1&limit=10 ACL: p2lab_stockly_warehouse_stock:read

grainOne row per
orderorder (the default)
binwarehouse + bin + license plate + order
backorderorder still owed goods

The response carries data, total, totalReserved, totalBackordered, page, limit and the grain it answered. An unknown grain is a 400, not a silent fallback.

These rows come from the reservation lines, the same figures the admin’s Reserved tile sums: a list and a tile that disagree is the bug this endpoint exists to prevent.

MethodPathAnswers
GETwarehouse/stock/batches/{productId}every lot of the product, with expiry
GETwarehouse/stock/{warehouseId}/batches/{productId}the same, in one warehouse
GETwarehouse/stock/placements/{productId}where its lots physically sit
GETwarehouse/stock/{warehouseId}/{binLocationId}/placements/{productId}the placements in one bin
GETwarehouse/stock/{warehouseId}/loose-bins/{productId}bins holding it loose, off any plate
GETwarehouse/{warehouseId}/stock/pickable-source/{productId}placements a pick may draw from
GETwarehouse/stock/product/{productId}/batch-historyhow its lots changed over time
GETwarehouse/batch-trace/{batchNumber}where one lot number went, across the network
GETwarehouse/order-lots/{orderId}which lots an order shipped

The last two are what a recall question actually needs: they answer “who received this lot” without a join written by hand.

GET|POST warehouse/stock/movements/{productId} ACL: p2lab_stockly_warehouse_stock_movement:read

The ledger for one product. POST takes a filter body for the cases a query string cannot express.

For anything report-shaped, the ledger is also an ordinary entity: /api/search/p2lab-stockly-warehouse-stock-movement with custom filters and aggregations is often less work than paging this endpoint.

MethodPathAnswersACL
GETwarehouse/order/{orderId}/allocationsper-line sourcing picture; add ?warehouseId= for the “outstanding elsewhere” verdictp2lab_stockly.viewer
GETwarehouse/order/{orderId}/stock-targetswhich node each line is booked againstp2lab_stockly_warehouse_stock:read
GETwarehouse/order/{orderId}/pick-planwhere a picker would be sentp2lab_stockly_warehouse_stock:read

pick-plan is a proposal and writes nothing, which makes it useful for showing a plan before anybody walks.

POST warehouse/{warehouseId}/bin-options ACL: p2lab_stockly.viewer

One page of bin picker options with their flags and ordering, assembled server-side. It exists because the alternative, pulling every bin and every stock row of the warehouse to the client and sorting there, is how a bin picker becomes unusable in a real warehouse.

Related: POST warehouse/{warehouseId}/bin-occupancy for how full bins are, and POST warehouse/{warehouseId}/suggest-bin for where a put-away would go.

Everything is a Shopware entity, and /api/search/… is the right tool for reporting, exports and any question with custom filters. What is lost is what these endpoints add on top: availability arithmetic that matches product.stock row for row, reservation figures taken from the lines rather than from the cache, and variant aggregation.

Two rules carry over from the packing API and apply just as much here:

Null is not zero. An absent value means nobody recorded one: an expiry of null is a lot with no shelf life on record, not one that expired.

Do not re-derive a published figure. If a number has an endpoint, read the endpoint. Every locally re-implemented “available” in the history of this module has eventually disagreed with the one the shop sells against.