SuiteScript & The Calculate Shipping Button

My job is to find ways to make other people’s job easier. One of my tasks involved calculating shipping from a custom Suitelet. This presented multiple obstacles. Here are my notes. I’m betting this will save both of us time if we ever need to come back to calculating shipping.

First, the code that calculates shipping works like this. It’s client-side code that builds an XML request which is sent to the server to do the heavy lifting. All of NetSuite’s client-side code is written in SuiteScript 1.0. It all ends with a server call that looks like this…

nlapiServerCall("app/common/shipping/dynshippingxml.nl", "getShippingRates", [xmlStr], null, 'POST');

As you can see, the client side code sends a request to the server where it passes xmlStr to getShippingRates. However, all the code which builds xmlStr is buried deep in the standard estimate Edit screen and not available in a custom Suitelet, unless you put it there yourself.

Trick number one, how to find the NetSuite’s client side code that handles building the xmlStr. Using Chrome Developer Tools, you can inspect the button and see which event it fires.

However, searching in Chrome Dev Tools to find calculateRates was futile. So here’s how I found it.

  • Edit an estimate (or sales order)
  • Open Dev Tools
  • In the console tab, type the following and hit enter. It exposes the calculateRates function, along with all the other code which builds the xmlStr parameter.

Once I could see the JavaScript, I could cut and paste it into a text file where I was able to rewrite the SuiteScript 1.0 code and integrate it into my client side JavaScript in my custom Suitelet.

Here is my sample code written in SuiteScript 2.0. It was derived from NetSuite standard “Calculate Shipping” code written in SuiteScript 1.0. If you jump to the bottom and review the calculateRate function, this is where it all begins. And please remember, THIS IS CLIENT-SIDE CODE. It will ultimately be fired by a button click or some other event in your custom Suitelet.

function isValEmpty(val) {
    if (val === null || val === undefined)
        return true;

    val = String(val);
    return (val.length === 0) || !/\S/.test(val);
}


function getExchangeRate(rec) {
    var dExchangeRate = rec.getValue('exchangerate');
    if (isNaN(dExchangeRate) || dExchangeRate.length < 1 || dExchangeRate < 0) {
        dExchangeRate = 1;
    }
    return parseFloat(dExchangeRate);
}

function isFulfillableItem(sItemType, bFulfillable) {
    var bIsFulfillableItem = false;
    if ('Assembly' == sItemType || 'InvtPart' == sItemType || 'Group' == sItemType || 'Description' == sItemType) {
        bIsFulfillableItem = true;
    } else if (('Service' == sItemType || 'OthCharge' == sItemType || 'GiftCert' == sItemType || 'NonInvtPart' == sItemType || 'Kit' == sItemType || 'DwnLdItem' == sItemType) && bFulfillable == 'T') {
        bIsFulfillableItem = true;
    }
    return bIsFulfillableItem;
}

function createRatesRequestXml(requestSrc, ratesCarrier, entityId, destCity, destState, destZip, destCountry, shipMethod, salesOrderId, isResidential, isThirdPartyAcct, thirdPartyCarrier, isPackagesReload, currency, tranFxRate, subsidiary, bIsItemLineRates, testId, nexusId, packages, items, isDefaultRequest, overrideShippingCost, isDynamicScriptingRequest, shipmentParameters) {
    var ratesReqXml = '<RatesRequest>';
    ratesReqXml += createXMLElement('RequestSrc', requestSrc);
    ratesReqXml += createXMLElement('RatesCarrier', ratesCarrier);
    ratesReqXml += createXMLElement('EntityId', entityId);
    ratesReqXml += createXMLElement('IsItemLineRates', bIsItemLineRates);
    ratesReqXml += createXMLElement('DestCity', destCity);
    ratesReqXml += createXMLElement('DestState', destState);
    ratesReqXml += createXMLElement('DestZip', destZip);
    ratesReqXml += createXMLElement('DestCountry', destCountry);
    ratesReqXml += createXMLElement('ShipMethod', shipMethod);
    ratesReqXml += createXMLElement('SalesOrderId', salesOrderId);
    ratesReqXml += createXMLElement('IsResidential', isResidential);
    ratesReqXml += createXMLElement('IsThirdPartyAcct', isThirdPartyAcct);
    ratesReqXml += createXMLElement('ThirdPartyCarrier', thirdPartyCarrier);
    ratesReqXml += createXMLElement('IsPackagesReload', isPackagesReload);
    ratesReqXml += createXMLElement('IsDefaultRequest', isDefaultRequest);
    ratesReqXml += createXMLElement('Currency', currency);
    ratesReqXml += createXMLElement('TransactionFxRate', tranFxRate);
    ratesReqXml += createXMLElement('Subsidiary', subsidiary);
    ratesReqXml += createXMLElement('TestId', testId);
    ratesReqXml += createXMLElement('NexusId', nexusId);
    ratesReqXml += createXMLElement('OverrideShippingCost', overrideShippingCost);
    ratesReqXml += createXMLElement('IsDynamicScriptingRequest', isDynamicScriptingRequest);
    ratesReqXml += getAdditionalShipmentXML(shipmentParameters);
    ratesReqXml += getPackagesXML(packages);
    ratesReqXml += getItemsXML(items);
    ratesReqXml += '</RatesRequest>';
    return ratesReqXml;
}

function createXMLElement(nodeName, nodeValue) {
    if (!isValEmpty(nodeValue)) { return ('<' + nodeName + '>' + xml.escape(nodeValue.toString()) + '</' + nodeName + '>'); } else { return '<' + nodeName + '/>'; }
}

function getAdditionalShipmentXML(parameters) {
    var result = '';
    if (parameters != null) {
        for (var paramKey in parameters) {
            result += createXMLElement(paramKey, parameters[paramKey]);
        }
    }

    return result;
}

function getPackagesXML(packages) {
    var packagesXml = '<Packages>';
    if (packages != null && packages.length > 0) {
        for (var i = 0; i < packages.length; i++) {
            var pkg = packages[i];
            if (pkg != null && pkg != undefined) {
                packagesXml += '<Package>';
                packagesXml += createXMLElement('PackageNumber', pkg['PackageNumber']);
                packagesXml += createXMLElement('PackageLength', pkg['PackageLength']);
                packagesXml += createXMLElement('PackageWidth', pkg['PackageWidth']);
                packagesXml += createXMLElement('PackageHeight', pkg['PackageHeight']);
                packagesXml += createXMLElement('PackageWeight', pkg['PackageWeight']);
                packagesXml += createXMLElement('PackageType', pkg['PackageType']);
                packagesXml += createXMLElement('PackageInsuredValue', pkg['PackageInsuredValue']);
                packagesXml += createXMLElement('PackageSignatureOption', pkg['PackageSignatureOption']);
                packagesXml += createXMLElement('AdditionalHandling', pkg['AdditionalHandling']);
                packagesXml += createXMLElement('UseCOD', pkg['UseCOD']);
                packagesXml += createXMLElement('CODAmount', pkg['CODAmount']);
                packagesXml += createXMLElement('CODMethod', pkg['CODMethod']);
                packagesXml += createXMLElement('DeliveryConfirmation', pkg['DeliveryConfirmation']);
                packagesXml += createXMLElement('DryIceWeight', pkg['DryIceWeight']);
                packagesXml += createXMLElement('DryIceUnit', pkg['DryIceUnit']);
                packagesXml += createXMLElement('CODTransportationCharges', pkg['CODTransportationCharges']);
                packagesXml += createXMLElement('CODOtherCharge', pkg['CODOtherCharge']);
                packagesXml += '</Package>';
            }
        }
    }
    packagesXml += '</Packages>';
    return packagesXml;
}

function getItemsXML(items) {
    var itemsXml = '<Items>';
    if (items != null && items.length > 0) {
        for (var i = 0; i < items.length; i++) {
            var item = items[i];
            if (item != null && item != undefined) {
                itemsXml += '<Item>';
                itemsXml += createXMLElement('ItemQuantity', item['ItemQuantity']);
                itemsXml += createXMLElement('ItemAmount', item['ItemAmount']);
                itemsXml += createXMLElement('ItemWeight', item['ItemWeight']);
                itemsXml += createXMLElement('ItemKey', item['ItemKey']);
                itemsXml += createXMLElement('ItemLocation', item['ItemLocation']);
                itemsXml += createXMLElement('ItemUnits', item['ItemUnits']);
                itemsXml += createXMLElement('ItemType', item['ItemType']);
                itemsXml += createXMLElement('ItemExcludeFromRateRequest', item['ItemExcludeFromRateRequest']);
                itemsXml += createXMLElement('ItemShipAddrKey', item['ItemShipAddrKey']);
                itemsXml += createXMLElement('ItemShipAddr1', item['ItemShipAddr1']);
                itemsXml += createXMLElement('ItemShipAddr2', item['ItemShipAddr2']);
                itemsXml += createXMLElement('ItemShipCity', item['ItemShipCity']);
                itemsXml += createXMLElement('ItemShipState', item['ItemShipState']);
                itemsXml += createXMLElement('ItemShipZip', item['ItemShipZip']);
                itemsXml += createXMLElement('ItemShipCountry', item['ItemShipCountry']);
                itemsXml += createXMLElement('ItemShipIsResidential', item['ItemShipIsResidential']);
                itemsXml += createXMLElement('ItemShipMethKey', item['ItemShipMethKey']);
                itemsXml += createXMLElement('ItemName', item['ItemName']);
                itemsXml += createXMLElement('ItemDescription', item['ItemDescription']);
                itemsXml += createXMLElement('ItemCountryOfManufacture', item['ItemCountryOfManufacture']);
                itemsXml += createXMLElement('ItemProducer', item['ItemProducer']);
                itemsXml += createXMLElement('ItemExportType', item['ItemExportType']);
                itemsXml += createXMLElement('ItemManufacturerName', item['ItemManufacturerName']);
                itemsXml += createXMLElement('ItemMultManufactureAddr', item['ItemMultManufactureAddr']);
                itemsXml += createXMLElement('ItemManufacturerAddr1', item['ItemManufacturerAddr1']);
                itemsXml += createXMLElement('ItemManufacturerCity', item['ItemManufacturerCity']);
                itemsXml += createXMLElement('ItemManufacturerState', item['ItemManufacturerState']);
                itemsXml += createXMLElement('ItemManufacturerZip', item['ItemManufacturerZip']);
                itemsXml += createXMLElement('ItemManufacturerTaxId', item['ItemManufacturerTaxId']);
                itemsXml += createXMLElement('ItemManufacturerTariff', item['ItemManufacturerTariff']);
                itemsXml += createXMLElement('ItemPreferenceCriterion', item['ItemPreferenceCriterion']);
                itemsXml += createXMLElement('ItemScheduleBNumber', item['ItemScheduleBNumber']);
                itemsXml += createXMLElement('ItemScheduleBQuantity', item['ItemScheduleBQuantity']);
                itemsXml += createXMLElement('ItemScheduleBCode', item['ItemScheduleBCode']);
                itemsXml += createXMLElement('ItemUnitsDisplay', item['ItemUnitsDisplay']);
                itemsXml += createXMLElement('ItemUnitPrice', item['ItemUnitPrice']);
                itemsXml += createXMLElement('ItemLine', item['ItemLine']);
                itemsXml += createXMLElement('ItemTotalQuantity', item['ItemTotalQuantity']);
                itemsXml += createXMLElement('ItemQuantityRemaining', item['ItemQuantityRemaining']);
                itemsXml += createXMLElement('ItemTotalAmount', item['ItemTotalAmount']);
                itemsXml += '</Item>';
            }
        }
    }
    itemsXml += '</Items>';
    return itemsXml;
}


function getShippingRate(quote_id) {
    var quote = record.load({
        type: record.Type.ESTIMATE,
        id: quote_id,
        isDynamic: true
    });

    var itemlinecount = quote.getLineCount('item');

    var bIsItemLineShippingRequest = (quote.getValue('ismultishipto') == 'T');
    var ratesType = quote.getValue('shipping_cost_function');
    if (!bIsItemLineShippingRequest && (ratesType == null || ratesType.length == 0)) {
        invalidateShippingCharge();
        return true;
    }
    var destCity = quote.getValue('shipcity');
    var destState = quote.getValue('shipstate');
    var destZip = quote.getValue('shipzip');
    var destCountry = quote.getValue('shipcountry');
    var shipMethod = quote.getValue('shipmethod');
    var currency = quote.getValue('currency');
    var isResidential = quote.getValue('shipisresidential');
    var isthirdpartyacct = 'F';
    var thirdpartyacct = '';
    var thirdpartycarrier = '';
    var items = new Array();
    var totalWeight = 0;
    var iladdrcount = -1;
    if (bIsItemLineShippingRequest) {
        iladdrcount = quote.getLineCount('iladdrbook');
    }
    for (var i = 0; i < itemlinecount; i++) {
        var itemtype = quote.getSublistValue('item', 'itemtype', i);
        var bFulfillable = quote.getSublistValue('item', 'fulfillable', i);
        if (!isFulfillableItem(itemtype, bFulfillable)) {
            continue;
        }
        var nkey = quote.getSublistValue('item', 'item', i);
        var weight = quote.getSublistValue('item', 'weightinlb', i);
        var quan = quote.getSublistValue('item', 'quantity', i);
        var amount = quote.getSublistValue('item', 'amount', i);
        var excludeFromRateRequest = quote.getSublistValue('item', 'excludefromraterequest', i);
        var location = parseInt(quote.getSublistValue('item', 'location', i));
        if (isNaN(location)) {
            location = getTranLocation();
        }
        if (weight == null || isNaN(weight) || weight.length < 1 || weight < 0) {
            weight = 0;
        }
        if (quan == null || isNaN(quan) || quan.length < 1 || quan < 0) {
            quan = 0;
        }
        if (amount == null || isNaN(amount) || amount.length < 1 || amount < 0) {
            amount = 0;
        }
        var nQuantity = parseInt(quan);
        if (bIsItemLineShippingRequest) {
            var itemshipaddr1 = '';
            var itemshipaddr2 = '';
            var itemshipcity = '';
            var itemshipstate = '';
            var itemshipzip = '';
            var itemshipcountry = '';
            var itemshipisresidential = '';
            var itemshipaddrkey = quote.getSublistValue('item', 'shipaddress', i);
            for (var iladdridx = 0; iladdridx < iladdrcount; iladdridx++) {
                var iladdrbookkey = quote.getSublistValue('iladdrbook', 'iladdrinternalid', iladdridx);
                if (itemshipaddrkey == iladdrbookkey) {
                    itemshipaddr1 = quote.getSublistValue('iladdrbook', 'iladdrshipaddr1', iladdridx);
                    itemshipaddr2 = quote.getSublistValue('iladdrbook', 'iladdrshipaddr2', iladdridx);
                    itemshipcity = quote.getSublistValue('iladdrbook', 'iladdrshipcity', iladdridx);
                    itemshipstate = quote.getSublistValue('iladdrbook', 'iladdrshipstate', iladdridx);
                    itemshipzip = quote.getSublistValue('iladdrbook', 'iladdrshipzip', iladdridx);
                    itemshipcountry = quote.getSublistValue('iladdrbook', 'iladdrshipcountry', iladdridx);
                    itemshipisresidential = quote.getSublistValue('iladdrbook', 'iladdrshipisresidential', iladdridx);
                    break;
                }
            }
            var itemshipmethkey = quote.getSublistValue('item', 'shipmethod', i);
        }
        var nUnits = 0;
        var nWeight = parseFloat(weight);
        if (!isNaN(nQuantity) && !isNaN(nWeight)) {
            weight = nWeight * nQuantity;
            totalWeight += weight;
        }
        if (amount && quan) {
            var itm = {};
            itm['ItemQuantity'] = quan;
            itm['ItemAmount'] = amount;
            itm['ItemWeight'] = weight;
            itm['ItemKey'] = nkey;
            itm['ItemLocation'] = location;
            itm['ItemUnits'] = nUnits;
            itm['ItemType'] = itemtype;
            itm['ItemExcludeFromRateRequest'] = excludeFromRateRequest;
            if (bIsItemLineShippingRequest) {
                itm['ItemShipAddrKey'] = itemshipaddrkey;
                itm['ItemShipAddr1'] = itemshipaddr1;
                itm['ItemShipAddr2'] = itemshipaddr2;
                itm['ItemShipCity'] = itemshipcity;
                itm['ItemShipState'] = itemshipstate;
                itm['ItemShipZip'] = itemshipzip;
                itm['ItemShipCountry'] = itemshipcountry;
                itm['ItemShipIsResidential'] = itemshipisresidential;
                itm['ItemShipMethKey'] = itemshipmethkey;
            }
            itm['Name'] = nlapiGetLineItemText('item', 'item', i);
            itm['ItemDescription'] = quote.getSublistValue('item', 'description', i);
            itm['Quantity'] = quote.getSublistValue('item', 'quantity', i);
            itm['ItemUnitPrice'] = quote.getSublistValue('item', 'rate', i);
            items.push(itm);
        }
    }
    if (ratesType == 'fedexRealTimeRate') {
        var fedexServiceName = quote.getValue('fedexservicename');
        if (totalWeight > getFedExMaxPackageWeight(fedexServiceName)) {
            if (!(fedexServiceName == 'FEDEX1DAYFREIGHT') && !(fedexServiceName == 'FEDEX2DAYFREIGHT') && !(fedexServiceName == 'FEDEX3DAYFREIGHT') && !(fedexServiceName == 'FIRSTOVERNIGHTFREIGHT') && !(fedexServiceName == 'INTERNATIONALECONOMYFREIGHT') && !(fedexServiceName == 'INTERNATIONALPRIORITYFREIGHT')) {
                alert(getFedExMaxWeightExceededMsg(fedexServiceName));
            }
        }
    }
    var subsidiary = '';
    var tranFxRate = getExchangeRate(quote);
    var entityId = quote.getValue('entity');
    var testId = quote.getValue('testid');
    var nexusId = quote.getValue('nexus');
    var isDefaultRequest = quote.getValue('isdefaultshippingrequest');
    var overrideShippingCost = quote.getValue('overrideshippingcost');
    var shipmentParameters = {};
    var reqXml = createRatesRequestXml('SALESORDER', ratesType, entityId, destCity, destState, destZip, destCountry, shipMethod, null, isResidential, isthirdpartyacct, thirdpartycarrier, false, currency, tranFxRate, subsidiary, bIsItemLineShippingRequest, testId, nexusId, null, items, isDefaultRequest, overrideShippingCost, false, shipmentParameters);

    return reqXml

}

function calculateRate(quote_id) {
    var xmlStr = getShippingRate(quote_id);

    var parameters = JSON.stringify({
        method: 'remoteObject.getShippingRates',
        params: [xmlStr]
    });

    https.post.promise({
        url: '/app/common/shipping/dynshippingxml.nl',
        body: parameters
    })
        .then(
            function (httpsResponse) {
                var objResult = JSON.parse(httpsResponse.body);
                var parser = new DOMParser();
                var xmlDoc = parser.parseFromString(objResult.result, "text/xml");
                var shippingRate = xmlDoc.getElementsByTagName('ShippingRate')[0].childNodes[0].nodeValue;
                alert(shippingRate);
            }
        )
        .catch(
            function onRejected(reason) {
                alert(reason);
            }
        )
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s