Inventory
Inventory is managed per location, with dynamic threshold rules, sales velocity & stockout forecasting, auto-reorder, and backorders/pre-orders.
Multi-location
inventory_levels/update webhooks record per-location stock at /content/commerce/inventory/levels/{inventory_item_id}.json (newest wins).
{
"inventory_item_id": "123456",
"locations": { "loc_1": { "available": 15, "updatedAt": "..." } }
}
- aggregates and per-location breakdowns come from
commerce.Locations - allocation planning (
commerce.Allocation) supportsmost_stock/prioritystrategies - config:
/etc/commerce/config/locations.yml(strategy/priorityOrder/defaultSafetyStock) - read:
GET …/endpoints/inventory-locations.groovy?productId=123[&variantId=456&qty=10]
Threshold rules
The effective threshold is resolved by precedence: manual override → rule match → default → none.
/etc/commerce/config/inventory-rules.yml:
default: 5
rules:
- name: "Perishable"
match: { productType: ["Food", "Beverage"] }
threshold: 20
- name: "High velocity"
match: { minVelocityPerDay: 5 }
threshold: 25
Match criteria: productType / vendor / tags (any match) / season (MM-DD window, wraps year-end) / minVelocityPerDay. Rules are evaluated top-down; the first rule whose criteria all hold wins.
Sales velocity & stockout forecast
A timer (every 6h by default) computes per-variant velocity (units/day) from order history and caches it at /content/commerce/analytics/velocity.json.
daysToStockout = current_stock / perDay
Variants predicted to run out within stockout.warnDays are alerted (debounced via commerce.Alerts). Config: /etc/commerce/config/velocity.yml (windowDays / stockout.warnDays / cooldownMinutes). Read: GET …/endpoints/forecast.groovy?warnDays=7.
Auto-reorder
A daily timer proposes purchase orders from velocity and stock.
need = velocity * (leadTimeDays + targetCoverDays) - currentStock
qty = roundUp(max(ceil(need), minOrderQty), roundTo)
Proposals are recorded at /content/commerce/purchase-orders/{yyyy}/{MM}/po_*.json (review_pending) and routed to an approval BPMN flow. On approval, the order is sent per supplier.delivery (none / email / webhook). Config: /etc/commerce/config/reorder.yml. Status: review_pending → approved/rejected → ordered/order_failed.
Backorders / pre-orders
At order time, a shortfall (shortfall) or a pre-order tag (preorder) is detected and recorded.
backordered → ready → released (normal)
backordered → cancelled (on refund)
- detection:
detectBackorders.groovyin the order route (idempotent byorder + line) - release: when stock arrives, oldest-first (FIFO) Release tasks are raised for what stock covers
- cancellation: on refund, the order's un-released backorders are cancelled
- records:
/content/commerce/backorders/{yyyy}/{MM}/backorder_{orderId}_{lineItemId}.json - config:
/etc/commerce/config/backorder.yml(preorderTags/notify.onCreated/notify.onReady)
The alert tool flow
A product webhook starts the inventory-alert workflow → on first run, if no threshold is set, a "Set Inventory Threshold" task is raised → when stock < threshold an "Inventory Review" task is raised → a notification is sent. If a workflow is already running for the same product, the new webhook is skipped. Handle tasks in the Webtop Tasks app.