Item back in stock
Fires when a committed inventory transaction takes an item's available physical quantity in a site and warehouse from zero or less to above zero. It's the transition-only counterpart of Inventory On-Hand Changed: instead of reporting every change, it reports the moment stock becomes available again in a warehouse, at most once per item, site and warehouse per transaction. Downstream systems can use it for back-in-stock notifications, re-enabling products on a storefront, or releasing orders that were held for stock.
| Event class | MGCLatticeItemBackInStockBusinessEvent |
| Contract class | MGCLatticeItemBackInStockBusinessEventContract |
| Category | Inventory management |
| Module | ModuleAxapta::Inventory |
| Label IDs | ItemBackInStockBusinessEventName / ItemBackInStockBusinessEventDescription |
| Trigger | InventUpdateOnhand.onOnHandUpdated delegate, sent after the transaction commits, when available physical for an item's site and warehouse moves from 0 or less to above 0 |
Trigger / Hook
- Handler: the
InventUpdateOnhand.onOnHandUpdateddelegate is handled byMGCLatticeInventOnHandChangedEventHandlers.onOnHandUpdated([SubscribesTo(classStr(InventUpdateOnhand), delegateStr(InventUpdateOnhand, onOnHandUpdated))]), shared with Inventory on-hand changed. It only queues the captured changes (MGCLatticeInventOnHandEventQueue::enqueue, in memory, no database access). The queued changes are raised after the commit byMGCLatticeInventOnHandEventQueue, which callsMGCLatticeItemBackInStockEventHandlers::raiseIfBackInStock(itemId, inventSiteId, inventLocationId, availPhysicalChange)for each item, site and warehouse. - Change capture:
MGCLatticeInventUpdateOnhand_Class_Extension([ExtensionOf(classStr(InventUpdateOnhand))]) wrapsprotected void insertInventSum(). Afternext insertInventSum(), while the transaction's pending on-hand changes (InventSumDeltarows) still exist, it callsMGCLatticeInventOnHandDeltaCollector::collect(), which sums them per item, site and warehouse for the current SQL transaction, including the net change in available physical (physical inventory minus physical reservations). The capture runs while either this event or Inventory on-hand changed is activated; one query serves both. - Firing condition: for each item, site and warehouse whose available physical went up in the transaction,
raiseIfBackInStockreads the available physical quantity for that site and warehouse after the commit (InventOnHandQty::newItemDim()with only the item, site, and warehouse flags set) and takes the value before the transaction as that quantity minus the net change. It sends the event when the value before is 0 or less and the value after is above 0. Because the quantity is read after the commit, a change committed by another session in between is included in both values. Both values are totals for the same site and warehouse, so:- a receipt split across several locations into an empty warehouse fires once;
- a move between two locations of one warehouse nets to zero and doesn't fire;
- each item, site and warehouse fires at most once per transaction.
- Reservations count: releasing a reservation can make stock available again and fire the event, and a receipt that is reserved immediately in the same transaction doesn't raise available physical and doesn't fire.
- Threshold: fixed at 0 in code (
MGCLatticeItemBackInStockEventHandlers::threshold()). There's no parameter to change it in this release. - Timing:
InventUpdateOnhandraisesonOnHandUpdatedfromttsNotifyPreCommit(), once per outermost transaction and legal entity, while the on-hand rows are locked. The events are therefore built and sent after the transaction has committed (SysTransactionScope.ttsCommitted), in the legal entity of the change. A transaction that rolls back sends nothing. Because sending happens after the commit, a failure while sending can't undo the inventory posting: it is reported to telemetry and the other events are still sent (at-most-once delivery). - Volume: the check runs for every on-hand update while the event is activated, but an event is sent only on the transition. No other throttling, parameter, or filter table applies.
- Enablement gate:
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeItemBackInStockBusinessEvent))is checked before the changes are captured and again, in the change's legal entity, before each event is sent.
public static void raiseIfBackInStock(
ItemId _itemId,
InventSiteId _inventSiteId,
InventLocationId _inventLocationId,
InventQty _availPhysicalChange)
{
if (_availPhysicalChange <= 0
|| !BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeItemBackInStockBusinessEvent)))
{
return;
}
InventQtyAvailPhysical thresholdQty = MGCLatticeItemBackInStockEventHandlers::threshold();
InventQtyAvailPhysical currentAvail = MGCLatticeItemBackInStockEventHandlers::availPhysical(_itemId, _inventSiteId, _inventLocationId);
InventQtyAvailPhysical previousAvail = currentAvail - _availPhysicalChange;
if (MGCLatticeItemBackInStockEventHandlers::isBackInStockTransition(previousAvail, currentAvail, thresholdQty))
{
MGCLatticeItemBackInStockBusinessEvent::newFromTransition(_itemId, _inventSiteId, _inventLocationId, previousAvail, currentAvail, thresholdQty).send();
}
}
Payload Contract
One row per [DataMember], in declaration order. The quantities are passed in by raiseIfBackInStock; the unit is looked up in initialize().
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | strLwr(curExt()) | Company whose on-hand changed, in lower case |
itemId | Item Number | ItemId | Captured change | |
siteId | Site | InventSiteId | Captured change | |
warehouseId | Warehouse | InventLocationId | Captured change | |
previousAvailablePhysical | Previous Available Physical | InventQtyAvailPhysical | Current available physical minus the transaction's net change in available physical for the site and warehouse | 0 or less when the event fires |
currentAvailablePhysical | Current Available Physical | InventQtyAvailPhysical | InventOnHandQty.availPhysical() for the item, site and warehouse | Greater than 0 when the event fires |
threshold | Threshold | InventQtyAvailPhysical | MGCLatticeItemBackInStockEventHandlers::threshold() | Always 0 in this release |
unitId | Unit | UnitOfMeasureSymbol | InventTable::find(itemId).inventUnitId() | Inventory unit of the item |
Every Lattice event also carries the Lattice ordering and correlation envelope, inherited from MGCLatticeBusinessEventContractBase: eventSequence (Int64 - increasing across the whole catalog and all companies, allocated when the event is raised inside the business transaction rather than when it commits; it orders the events of one business record, gaps are permanent and normal, and it must not be used to re-sequence the whole stream), correlationId (String, always Scope:Key - groups emissions that belong to one logical run; Event:<GUID> unique to this emission when the event belongs to no run) and schemaVersion (Int - the payload shape version; 1 for this contract). D365 F&O also adds its standard envelope fields to every business event payload: BusinessEventId, BusinessEventLegalEntity, ContextRecordSubject, ControlNumber, EventId, EventTime, EventTimeIso8601, InitiatingUserAADObjectId, MajorVersion, MinorVersion, and ParentContextRecordSubjects. Do not rely on the order of members in the delivered JSON.
Example Payload
{
"BusinessEventId": "MGCLatticeItemBackInStockBusinessEvent",
"ControlNumber": 5637146301,
"EventId": "7A2F4C18-9E3B-4D07-B651-0C8E2F9A4B73",
"EventTime": "\/Date(1790255126000)\/",
"EventTimeIso8601": "2026-09-24T13:05:26.1038870Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{603C4947-6E4E-E514-DD8A-E0269004135F}",
"ParentContextRecordSubjects": [],
"eventSequence": 48402,
"correlationId": "Event:030ee572-e907-4012-8af5-2415a47f671b",
"schemaVersion": 1,
"legalEntity": "usmf",
"itemId": "A0001",
"siteId": "1",
"warehouseId": "11",
"previousAvailablePhysical": 0.0,
"currentAvailablePhysical": 48.0,
"threshold": 0.0,
"unitId": "ea"
}
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Item back in stock") or by filtering the Category column to Inventory management. Select it and use Activate to bind it to a configured endpoint (for example, an Azure Service Bus queue, Event Grid topic, or HTTPS webhook).
No other setup is needed; the event doesn't depend on Inventory On-Hand Changed being activated.