How to configure my stock rule in Hardware Asset Mgt. so that it runs on the basis of Model Category instead of Model?

Aditya Sinha
Tera Contributor

Hello, I have a requirement that for the stockrule form in Hardware Asset Mgt. I need to replace the model field with the model category and configure the stockrule so that it works according to model category. I am attaching the scheduled job which triggers the stockrule - (Open to any and all suggestions)

Scheduled Job - Stock Rule Runner 

Script - processStockRules 

Start of processStockRules

var ProcessStockRules = Class.create();
ProcessStockRules.prototype = Object.extendsObject(global.AssetManagementPerGlideRecordBaseJob, {
CONS_MODEL: 'cmdb_consumable_product_model',
TRACK_CONS: 'track_as_consumable',
initialize: function() {
this.transfer = new StockRuleTransfer();  // I have tagged this script below after end of prcoessStockRules
},
getRecords: function() {
var stockRules = new GlideRecord('alm_stock_rule');
stockRules.addQuery('active', 'true');
stockRules.orderBy('restocking_option');
stockRules.query();
return stockRules;
},
runJobForRecord: function(stockRuleGr) {
if (stockRuleGr.getValue('restocking_option') === 'stockroom') {
this._processStockoomStockrule(stockRuleGr);
} else {
this._processVendorStockrule(stockRuleGr);
}
},
_processStockoomStockrule: function(stockRuleGr) {
var parent = stockRuleGr.parent_stockroom;
var stockroom = stockRuleGr.stockroom;
var model = stockRuleGr.model;
var supply = this.transfer.getTotalRecordCount(stockroom, model); //edit this line ,model info,need to edit to model category
var thresh = parseInt(stockRuleGr.threshold, 10);
var need = thresh - supply;
var order = 0;
var size = parseInt(stockRuleGr.order_size, 10);
if (need > 0) {
if (size <= 0) {
order = need;
} else {
while (order < need) {
order += size;
}
}
var stock = this.transfer.checkStockroomTransferAvailability(parent, model);
if (stock >= order) {
if (model.sys_class_name.toString() === this.CONS_MODEL
|| model.asset_tracking_strategy.toString() === this.TRACK_CONS) {
this.transfer.consumableTransfer(parent, stockroom, model, order);
} else {
this.transfer.assetTransfer(parent, stockroom, model, order);
}
}
// if it does not have enough, it will run each night until the parent stockroom has enough supply
}
},
_processVendorStockrule: function(stockRuleGr) {
/*
If quantity is below threshold and pending_delivery is false, send email & create task/purchaseOrder
If quantity is below threshold and pending_delivery is true, do nothing
If quantity is above threshold and pending_delivery is false, do nothing
If quantity is above threshold and pending_delivery is true, change pending_delivery to false
*/
var threshold = stockRuleGr.threshold;
var stockroom = stockRuleGr.stockroom;
var model = stockRuleGr.model;
var quantity = this.transfer.getTotalRecordCount(stockroom, model);
var size = parseInt(stockRuleGr.order_size, 10);
// Quantity below threshold
if ((quantity < threshold) && stockRuleGr.pending_delivery.toString() !== 'true') {
var need = threshold - quantity;
var order = 0;
if (size <= 0) {
order = need;
} else {
while (order < need) {
order += size;
}
}
// Trigger event
gs.eventQueue('asset.restock', stockRuleGr, order, threshold);
// STRY52899577 - If model belongs to EAM, order is handled by EAM
if (GlidePluginManager.isActive('com.sn_eam') && sn_eam.EAMUtils.isEnterpriseModel(model)) {
sn_eam.StockOrderUtils.createStockOrderReq(stockRuleGr, quantity, order);
} else if (GlidePluginManager.isActive('com.sn_hamp') && !sn_hamp.HAMUtils.checkIfEAMClass(stockRuleGr.model.sys_class_name, sn_hamp.HAMConstants.EAM_MODEL_BASE_CLASS)) {
sn_hamp.StockOrderUtils.createStockOrderReq(stockRuleGr, quantity, order);
} else {
this._createPurchaseOrder(stockRuleGr, quantity, order);
}
// Set "pending_delivery" to true
stockRuleGr.pending_delivery = 'true';
} else if ((quantity > threshold) && stockRuleGr.pending_delivery.toString() === 'true') {
// Quantity above threshold
stockRuleGr.pending_delivery = 'false';
}
stockRuleGr.update();
},
_createPurchaseOrder: function(stockLevel, quantity, order) {
var stockroom = stockLevel.stockroom;
var model = stockLevel.model;
var threshold = stockLevel.threshold;
// Add Task
// we keep adding task because:
// if procurement plugin is not enabled, then we create TASK for existing Stock Rule records
// if procurement plugin is enabled, then we create TASK for the user who have been used to TASK
var task = new GlideRecord('task');
task.initialize();
task.assigned_to = stockroom.manager;
task.short_description = 'Quantity threshold breached: ' + stockroom.name;
task.description = 'Stockroom: ' + stockroom.name + '\nItem: ' + model.display_name + '\nQuantity: '
+ quantity + '\nThreshold: ' + threshold;
task.insert();
if (GlidePluginManager.isActive('com.snc.procurement')) {
// Add Purchase Order
var po = new GlideRecord('proc_po');
po.initialize();
po.ship_to = stockroom;
po.short_description = 'Quantity threshold breached: ' + stockroom.name;
po.description = '-- Created by Stock Rule --\nStockroom: ' + stockroom.name + '\nItem: '
+ model.display_name + '\nQuantity: ' + quantity + '\nThreshold: ' + threshold;
var poId = po.insert();
// Add Purcahse Order Line Item
var poli = new GlideRecord('proc_po_item');
poli.initialize();
poli.purchase_order = poId;
poli.model = model;
poli.list_price = model.cost;
poli.cost = model.cost;
poli.ordered_quantity = order;
poli.total_cost = order * model.cost;
poli.insert();
}
},
getRefQualCondition: function(stockRuleGr) {
/*
* Description: Used as RefQual for model and stockroom list in Stock Rule when domain separation is enabled
*/
if (this.fIsDomainDataSeparationEnabled) {
return stockRuleGr.getValue('sys_domain') === 'global' ? 'sys_domain=global' : 'sys_domain='
+ stockRuleGr.sys_domain + '^ORsys_domain=global';
}
return '';
},
type: 'ProcessStockRules',
});

end of processStockRules

StockRuleTransfer start

var StockRuleTransfer = Class.create();
StockRuleTransfer.prototype = {
initialize : function() {
},

/**
* creates transfer order with a single consumable transfer order line
*/
consumableTransfer : function(parent, stockroom, model, amount) {
var to = new GlideRecord("alm_transfer_order");
to.initialize();
to.from_stockroom = parent;
to.to_stockroom = stockroom;
to.from_location = parent.location;
to.to_location = stockroom.location;
var transferId = to.insert();

var tol = new GlideRecord("alm_transfer_order_line");
tol.initialize();
tol.transfer_order = transferId;
tol.model = model; //cooment this line and see effect , //edit this line ,model info,need to edit to model category
tol.quantity_requested = amount;
tol.insert();
},

/**
* creates transfer order with multiple asset transfer order line
*/
assetTransfer : function(parent, stockroom, model, amount) {//edit to
var to = new GlideRecord("alm_transfer_order");
to.initialize();
to.from_stockroom = parent;
to.to_stockroom = stockroom;
to.from_location = parent.location;
to.to_location = stockroom.location;

var transferId = "";

var gr = new GlideRecord("alm_asset");
gr.addQuery("model", model); //try fetching on the basis of model category and see if t.o is generated
gr.addQuery("stockroom", parent);
gr.addQuery("install_status", "6");
gr.addQuery("substatus", "available");
global.AssetUtils.addAssetQuery(gr, global.AssetUtils.ASSET_FUNCTION_FEATURE.STOCK_RULE);
gr.query();

for ( var i = 0; i < amount; ++i) {
if (gr.next()) {

if (i == 0) {
transferId = to.insert();
}

var tol = new GlideRecord("alm_transfer_order_line");
tol.initialize();
tol.transfer_order = transferId;
tol.model = model; //same as line 21
tol.asset = gr.sys_id;
tol.insert();
} else {
gs.addErrorMessage(gs.getMessage("Parent stockroom ran out of assets to transfer. Aborting transfer"));
break;
}
}
},

checkStockroomAvailability : function(stockroom, model) {
// retrieve all matching assets in the stockroom
var gr = new GlideRecord("alm_asset");
gr.addQuery("stockroom", stockroom);
gr.addQuery("model", model);
gr.addQuery("install_status", "6");
gr.addQuery("substatus", "available").addOrCondition("substatus",
"reserved");
gr.addQuery('active_to', 'false').addOrCondition('active_to', null);
global.AssetUtils.addAssetQuery(gr, global.AssetUtils.ASSET_FUNCTION_FEATURE.STOCK_RULE);
gr.query();
// count quantity
var count = 0;
while (gr.next())
count += parseInt(gr.quantity,10);

return count;
},

checkStockroomTransferAvailability : function(stockroom, model) {
// retrieve all matching assets in the stockroom
var gr = new GlideRecord("alm_asset");
gr.addQuery("stockroom", stockroom);
gr.addQuery("model", model);
gr.addQuery("install_status", "6");
gr.addQuery("substatus", "available");
gr.addQuery('active_to', 'false').addOrCondition('active_to', null);
global.AssetUtils.addAssetQuery(gr, global.AssetUtils.ASSET_FUNCTION_FEATURE.STOCK_RULE);
gr.query();

// count quantity
var count = 0;
while (gr.next())
count += parseInt(gr.quantity,10);

return count;
},

getTotalRecordCount : function(stockroom, model) {
return this.checkStockroomAvailability(stockroom, model) +
this.transferOrderAvailability(stockroom, model);
},

/**
* retrieves record count for assets that are currently in a transfer order
* going to that stockroom
*/
transferOrderAvailability : function(stockroom, model) {

var tols = new GlideRecord('alm_transfer_order_line');
tols.addQuery('transfer_order.to_stockroom', stockroom);
tols.addQuery('model', model);
tols.addQuery('stage', 'NOT IN', 'received,delivered,cancelled');
tols.query();

var count = 0;
while (tols.next()) {

if (global.AssetUtils.isAssetLoanerInTOL(tols)) {
continue;
}
if (!tols.quantity_remaining.nil())
count += parseInt(tols.quantity_remaining,10);
else
count += parseInt(tols.quantity_requested,10);

count -= parseInt(tols.quantity_returned,10);
}

return count;
},

type : 'StockRuleTransfer'
};

END

1 REPLY 1

Daniel Slocum
ServiceNow Employee
ServiceNow Employee

You will need to run a glide aggregate count of the number of assets related to a model within the model category and then bounce that result against the stock rule's threshold.  What complicates this is that the model category attribute on an asset record is a slush bucket.  I don't have sample script for you.

I have done something similar in the past where I totaled the number of assets that were either a specific model or a substitute model related to the model. If you struggle to query through the slush bucket, relating substitute models might be a simpler approach.

This is the sample script for working with a specific model designated in the stock rule but measuring that model and any related substitutes. The script was created in Geneva or Helsinki.  If you want to use it you'll need to give it rigorous testing for compatibility with current ServiceNow releases. 

/*  Takes into account both the model and model substitues when calculating Vendor Order Stock Rules.
    Is not currently configured to take Substitutes into account for Transfer order stock rules.
    To Utilize:
    Create new Scheduled Job and set "Stock Rule Runner" to trigger "on demand" 
    or replace "Stock Rule Runner" script with the script below.
*/    

gs.include('StockRuleTransfer');
        var transfer = new StockRuleTransfer();
        
        runStockRules();
        stockLevelThreshold();
        
        function runStockRules() {
        
            var stockRules = new GlideRecord('alm_stock_rule');
            stockRules.addQuery('active', 'true');
            stockRules.addQuery('restocking_option', 'stockroom');
            stockRules.query();
        
            while (stockRules.next()) {
        
                var parent = stockRules.parent_stockroom;
                var stockroom = stockRules.stockroom;
                var model = stockRules.model;
                var supply = transfer.getTotalRecordCount(stockroom, model);
                var thresh = parseInt(stockRules.threshold,10);
                var need = thresh - supply;
                var order = 0;
                var size = parseInt(stockRules.order_size,10);
        
                if (need > 0) {
        
                    if(size <= 0) {
                        order = need;
                    } else {
                        while (order < need)
                            order += size;
                    }
        
                    var stock = transfer.checkStockroomTransferAvailability(parent, model);
                    if (stock >= order) {
        
                        if(model.sys_class_name == 'cmdb_consumable_product_model' || model.asset_tracking_strategy == 'track_as_consumable')
                            transfer.consumableTransfer(parent, stockroom, model, order);
                        else
                            transfer.assetTransfer(parent, stockroom, model, order);
                    }
                    //if it does not have enough, it will run each night until the parent stockroom has enough supply
                }
            }
        }
        
        function stockLevelThreshold() {
            /*
            If quantity is below threshold and pending_delivery is false, send email & create task
            If quantity is below threshold and pending_delivery is true, do nothing
            If quantity is above threshold and pending_delivery is false, do nothing
            If quantity is above threshold and pending_delivery is true, change pending_delivery to false
            Account for model substitues in calculation
            */
        
            var stockLevel = new GlideRecord('alm_stock_rule');
            stockLevel.addQuery('active', 'true');
            stockLevel.addQuery('restocking_option', 'vendor');
            stockLevel.query();
        
           while (stockLevel.next()) {
        
            var threshold = stockLevel.threshold;
            var stockroom = stockLevel.stockroom;
            var model = stockLevel.model;
            var quantity = 0;
                var rec = new GlideRecord('cmdb_m2m_model_substitute');
                rec.addQuery('model', model);
                rec.query();
                if (rec.getRowCount() != 0){
                while (rec.next()){
                  var subModel = rec.substitute;
                  quantity = quantity + transfer.getTotalRecordCount(stockroom, subModel);
                   }
                }         
            quantity = quantity + transfer.getTotalRecordCount(stockroom, model);
            var manager_email = stockLevel.stockroom.manager.email;
            var manager_name = stockLevel.stockroom.manager.name;
            var size = parseInt(stockLevel.order_size,10);
        
            // Quantity below threshold
            if ((quantity < threshold) && stockLevel.pending_delivery != true) {
                var need = threshold - quantity;
                var order = 0;
        
                if(size <= 0) {
                    order = need;
                } else {
                    while (order < need)
                    order += size;
                }
        
                // Trigger event
                gs.eventQueue("asset.restock", stockLevel, order, threshold);
        
                // Add task
                var task = new GlideRecord('task');
                task.initialize();
                task.assigned_to = stockLevel.stockroom.manager;
                task.short_description = 'Quantity threshold breached: '+ stockroom.name;
                task.description = 'Stockroom: ' + stockroom.name + '\nItem: ' + model.display_name + '\nQuantity: ' + quantity + '\nThreshold: ' + threshold;
                task.insert();
        
                // Set "pending_delivery" to true
                stockLevel.pending_delivery = "true";
            }
        
            // Quantity above threshold
            else if ((quantity > threshold) && stockLevel.pending_delivery == true) {
                stockLevel.pending_delivery = "false";
            }
            stockLevel.update();
          }
}