Skip to content

Corrections and bulk jobs

The four write endpoints book one movement each. What follows books many, or books a difference rather than a quantity, or does not finish inside the request at all.

POST warehouse/stock/count ACL: p2lab_stockly.execute_stock_operations

Sets locations to what somebody counted. The endpoint takes a whole sheet, not one line:

{
"warehouseId": "0191f1…",
"level": "aggregate",
"comment": "Cycle count, aisle A",
"lines": [
{ "productId": "0191f3…", "binLocationId": "0191f2…", "countedQty": 22 },
{ "productId": "0191f8…", "binLocationId": "0191f2…", "countedQty": 0 }
]
}

level is aggregate (the bin’s total for that product) or batch. At batch level each line may also carry batchId, batchNumber and expiresAt.

The answer reports what each line actually changed:

{ "success": true, "results": [
{ "productId": "0191f3…", "binLocationId": "0191f2…", "batchId": null, "delta": -2 }
] }

Only the difference is booked, as a count movement. A line whose counted figure already matches books nothing and reports delta: 0. A count of zero is legitimate, because an empty shelf has to be bookable, but a negative one is refused with P2LAB_STOCKLY__STOCK__NEGATIVE_COUNT: a physical count has no direction.

The system quantity is re-read when the correction is applied, not when the count was taken, so the result equals the counted figure however much drifted in between.

POST warehouse/stock/batch/{batchId}/correct ACL: p2lab_stockly.execute_stock_operations

Re-labels a lot: its number, its expiry. Stock-neutral: it books a batch_correction movement with quantity zero, so the product’s history shows when a lot was re-dated without ever implying that units moved.

MethodPathWhat it doesACL
POSTwarehouse/stock/delete-emptydrops one empty stock row by stockIdp2lab_stockly.execute_stock_operations
POSTwarehouse/stock/sync-product/{productId}recalculates product.stock from the shelvesp2lab_stockly.execute_stock_operations

delete-empty refuses with 409 not_deletable when the row still holds stock or reserved units. The guard is in the delete statement itself, so a row that became meaningful a moment ago cannot be removed by a request that was already in flight.

sync-product is the repair for a product.stock somebody else overwrote. It needs no payload and is safe to repeat, because it derives the value rather than adjusting it.

Four operations return a progressId immediately and continue in the background:

MethodPathStartsACL
POSTwarehouse/stock/initialize/{warehouseId}seeds stock rows from product.stockp2lab_stockly.execute_stock_operations
POSTwarehouse/stock/transfer-bulkdrains one warehouse into anotherp2lab_stockly.execute_stock_operations
POSTwarehouse/{warehouseId}/auto-assign-binsgives every unassigned row a suggested binp2lab_stockly.execute_stock_operations
POSTwarehouse/stock/sync-variantsrolls variant stock up to parentsp2lab_stockly.execute_stock_operations

Each answers {"success": true, "progressId": "0191f9…"}. Poll it:

GET /api/_action/p2lab-stockly/warehouse/stock/job/{progressId}/status
{ "progressId": "0191f9…", "status": "running",
"totalItems": 4812, "processedItems": 1200, "error": null }

initialize and sync-variants have status routes of their own, warehouse/stock/initialize/{progressId}/status and warehouse/stock/sync-variants/{progressId}/status, with the same response shape. All three status routes need p2lab_stockly_task_progress:read, which is a different privilege from the one that started the job: a system that may watch is not automatically one that may act.

An unknown progressId is a 404.

initialize copies product.stock onto the warehouse’s unassigned bin, and it is deliberately narrow about what it touches:

  • leaf products only, because a variant parent’s stock is the sum of its children, so seeding both would count everything twice;
  • products with stock above zero;
  • products not already tracked in any warehouse, because product.stock is the total across the whole network, so seeding an article that already lives somewhere else would copy its whole quantity a second time and the next recalculation would inflate the figure.

It is a one-time onboarding step, not a synchronisation. Running it twice adds nothing, which is the intent.

p2lab_stockly.execute_stock_operations covers the ordinary write path. Four operations are gated separately because they are decisions rather than movements:

PrivilegeGuards
p2lab_stockly.commit_stocktakecommitting or reverting a stock-check session
p2lab_stockly.variance_approveapproving lines a variance gate held back
p2lab_stockly.reassign_binsmoving a bin, with its stock, to another warehouse
p2lab_stockly.run_integrity_fixapplying an integrity repair, and adopting open orders

An integration that only books movements should not be granted any of the four.