Extended Inventory Synchronization

In some specific scenarios, you can send holds for inventory when an order basket is submitted in B2C Commerce, and remove the holds when inventory imports into Order Management. This can require adding logic in the B2C Commerce basket workflow.

Consider the following best practices when extending the inventory synchronization process:

Note: Order Management exposes inventory endpoints in the REST APIs, which can be accessed during basket workflows via script API calls from the storefront.

EXAMPLES

High-level flow for additional code logic.

//in placeorder controller
function start(){ 
    var cart = Cart.get();

    //... run checks on the cart to ensure it is valid

    var order = cart.createOrder();

    // Apply a hold in OMS to reserve inventory: assignOMSInventoryHolds()

        try{      
        
    // Check inventory levels of each product to ensure it hasn't been oversold
    // * Best done in OrderModel.js, see samples below 

        // Note: It's better to remove a hold in Order Management 
        //  than reverse an authorization against a payment method. 
        //If the place order process aborts once you place the hold,
        // remove the hold.
    
    //... Payment auth/capture code 

  
    // .. any other order creation logic
    }catch(e){
        //... error logging/handling
        // Remove Hold from OMS if order create fails, RemoveAllHolds()
        
        return {
                error: true,
                PlaceOrderError: new Status(Status.ERROR, 'error.order.submit')
            };
    }

    Order.submit(order);
}

Use this sample code as a guide.

Note: This inventory check considers an inventory quantity of zero as a valid value because if there was a quantity of one remaining that was reserved before the inventory check inventory would come back as 0.
//in placeorder controller
var InventoryMgr = require('dw/om/inventory/InventoryMgr')

function assignOMSInventoryHolds(order){
    var plis = order.getProductLineItems();
    //For each product line item on the order
    for (var k in plis) {
        //using a hold request
        var holdRequestObject = new dw.om.inventory.InventoryAddHoldsRequest()
        var holdArray = [];
           
        var pli = plis[k],    
            product = pli.getProduct(),
            lineItemQty = pli.getQuantity();
        
        // REQUIRED: client-specific expire time.
        // should be as small as reasonably possible 
        // to allow for order import into OMS (in worst case)
        // If you have will always submit the order right away, then hours
        // If you have hold the order here for a while then, 
        // must be long enough to cover those processes.  
        var expireDate = new Date();
        expireDate = <%something%>; //REQUIRED: your expiration here

        var siteID = new dw.om.common.ObjectKey();
        var prodItem = new dw.om.common.ObjectKey();
        siteID = dw.system.Site.getCurrent().getID();
        prodItem.id = product.getID();

        var holdRequest= new dw.om.inventory.InventoryAddHoldsRequestData();
        holdRequest.setItem(prodItem);
        holdRequest.setSite(siteID);
        holdRequest.setQuantity(lineItemQty);
        holdRequest.setHoldType(dw.om.common.InventoryHoldTypeEnum.WEB);
        holdRequest.setExpiration(expireDate); //very important to set this
        holdRequest.setDescription(dw.util.StringUtils.format(
                'qty: {0}; SKU {1}; until {2}',
                lineItemQty, product.getID(), expireDate));
        holdArray.add(holdRequest);
    }
    holdRequestObject.setHolds(holdArray);
    
    var holdResult = InventoryMgr.addHolds(holdRqst);
//function continued from above block
    if ('completed' in holdResult && holdResult.completed) {
        var holdList = new dw.util.ArrayList(holdResult.holds);
        
        if (holdList.length > 0) {
            for (var z in holdList) {
                var hold = holdList[z],
                guid = hold.holdGuid;
                
                if (guid != null) {
                    for (var p in plis) {
                        var prodLineItem = productLineItems[p];
                        
                        if (hold.item.id === prodLineItem.getProduct().getID()) {
                            dw.system.Transaction.wrap(function () {
                                prodLineItem.getCustom().omItemHoldGuid = guid;
                            })
                        }
                    }
                }
            }
        }
    }
}
Note: If at any time during the rest of the basket process, such as while handling payments or final submission, an error prevents the basket from submitting the order, you should remove the holds in Order Management. This action prevents inventory from being undersold due to invalid holds.
//in placeorder controller
var Order = app.getModel('Order');

function checkForInventory(){

     try {
        var pliLineItems = order.getProductLineItems().toArray();
        var productsMissingInventory = Order.checkForProductsWithNoInventory(pliLineItems);
        if (productsMissingInventory) {
            failPlacedOrder(CO_PLACE_ORDER_ERROR_TYPES.OMS_CHECK, order);
            return Transaction.wrap(function () {
                return {
                    error: true,
                    PlaceOrderError: new Status(Status.ERROR, 'confirm.error.inventory')
                };
            });
        }
    }
    catch (e) {
        //.. handle errors checking inventory
    }
}
// in OrderModel.js
var dw_om_items = require('dw/om/items');
var dw_om_common = require('dw/om/common');
var ItemMgr = require('dw/om/items/ItemMgr');
var FilterEnum = require("dw/om/common/SyncFilterEnum"); 
var retHashMap = new dw_util.HashMap();

function checkForProductsWithNoInventory(){ 
    
    var productList : String;
    var productList : String;
    
     for (idx=0; idx<arrProductIds.length; idx++) {
        productId = arrProductIds[idx];
        if (productList != "") {
            productList = (productList + ",");
        }
        productList = productList + ("'" + productId + "'");
    }
        
    var searchRequest = dw_om_items.ItemSearchRequest();
    searchRequest.count = 50;
    searchRequest.select = "cd,guid,quantity";
    searchRequest.query = "cd IN (" + productList + ")";
    searchRequest.sync = new dw_om_common.SyncData();
    searchRequest.sync.filter = FilterEnum.NONE;
    
    var searchResult = ItemMgr.search(searchRequest);
    //check for null searchResult and null properties here
    
    for (idx=0; idx<searchResult.data.length; idx++) {
        if(searchResult.data[i].quantity < 0)
          return true;
    }
}
//in placeorder controller
var InventoryMgr = require('dw/om/inventory/InventoryMgr'),
        holdRqst = new dw.om.inventory.InventoryRemoveHoldsRequest(),
        holds = [];
        
function RemoveAllHolds(productLineItems){
    for (var k in productLineItems) {
        var guid = productLineItems[k].getCustom().omItemHoldGuid;
    
        if (guid != null) {
            var holdRqstData = new dw.om.inventory.InventoryRemoveHoldRequestData(),
                prodItem = new dw.om.common.ObjectKey();
    
            prodItem.id = productLineItems[k].getProduct().getID();
            
            holdRqstData.holdGuid = guid;
            holdRqstData.item = prodItem;
            
            holds.push(holdRqstData);
        }
    }
    
    holdRqst.setHolds(holds);
    
    var holdResult = InventoryMgr.removeHolds(holdRqst);
    
    //if the holds were removed in OM, remove hold guid values for the item attributes
    if (holdResult.completed) {
        for (var k in productLineItems)
        {
            Transaction.wrap(function() {
                productLineItems[k].getCustom().omItemHoldGuid = null;
            })
        }
    }
}