Though similar to a Mass Update script, the Map/Reduce script type steps up the game. Last week we discussed the theory behind the new script type (you can read part 1 here), but today we will provide a guide for how to create a basic Map/Reduce script yourself.

The Fundamentals

Just by way of review, there are five stages to the Map/Reduce script type, though not all are required.

  1. Get-Input-Data Stage
  2. Map Stage
  3. Shuffle Stage
  4. Reduce Stage
  5. Summarize Stage

Before jumping into the rest of this post, It is important to note that only two functions are necessary for us when developing the most basic Map/Reduce script. Today, we’ll learn how to use these two essential functions. The goal of our basic script example is simply to locate all Sales and Purchase Orders since 2020. The script will then load and save each record, causing any existing User Event scripts on those records to actuate.

Our File Structure

From a practical perspective, I first want to provide the layout for creating a Map/Reduce script.

define(['N/search', 'N/record'], function (search, record) {
    /**
     * Map/Reduce script to populate appropriate channel field on Estimate Records.
     *
     * @NApiVersion 2.x
     * @NScriptType MapReduceScript
     * @author Ben Rogol (SuiteRep)
     */
    var exports = {};

    /**
     * @function getInputData Marks the beginning of the script’s execution. The purpose of the input stage is to generate the input data.
     * Executes when the getInputData entry point is triggered. This entry point is required.
     * @param {Object} context
     * @param {Boolean} context.isRestarted Indicates whether the current invocation of the getInputData(inputContext) function represents a restart.
     * @param {Object} context.ObjectRef And object that contains the input data.
     * @param {Object} context.ObjectRef.id The internal ID or script ID of the object. For example, this value could be a saved search ID.
     * @param {Object} context.ObjectRef And object that contains the input data.
     *
     * @return search.Search
     * @since 2015.2
     */
    function getInputData(context) {

    }


    /**
     * @function map The logic in your map function is applied to each key/value pair that is provided by the getInputData stage. One key/value pair is processed per function invocation, then the function is invoked again for the next pair. 
The output of the map stage is another set of key/value pairs. During the shuffle stage that always follows, these pairs are automatically grouped by key.
     *
     * @param {Object} context
     * @param {string} context.key
     * @param {Array} context.value
     *
     * @return {void}
     * @since 2015.2
     */
    function map(context) {

    }


    exports.getInputData = getInputData;
    exports.map = map;
    return exports;
});

The outer shell of the script should appear familiar. We load in any needed modules and then export our primary functions at the end. It is important to export each of the standard functions that you use in a Map/Reduce script. Just like any other entry-point function, NetSuite needs to know about these so it can call them when the time is right.

The Get-Input Stage

Now, let’s fill in those two functions. For instance, here’s an example of what you could do:

 function getInputData(context) {
    var filters = [
          ["type", search.Operator.ANYOF, ['SalesOrd', 'PurchOrd']],
          "and",
          ["mainline", "is", "T"],
          "and",
          ["trandate", "after", "12/31/2019"],
      ];

      return search.create({ type: search.Type.TRANSACTION, filters: filters });
  }

The get-input function is really quite simple. In order to get input, we can create a search. After that search has been created, we return it. And that’s it. Because the NetSuite system will handle this asynchronously for the Map stage, there is no need to run the search or loop over the results

The Map Stage

Now that NetSuite has the data it needs, it can feed those results asynchronously into the map() function. It is now possible to perform an action on each of these results if desired. This is what we will do in our example, since we are trying to keep things simple. The fullest use of the Map/Reduce function could place the primary actions into the Reduce stage. But for now we can perform the main action in the Map stage for simplicity.

function map(context) {
      let result = JSON.parse(context.value);
      log.debug('result', result);

      const recordObject = record.load({ type: result.recordType, id: result.id });
      log.debug("recordObject", recordObject);

      // You could do other automations on the record here too.

      try {
          recordObject.save({ enableSourcing: true, ignoreMandatoryFields: true });
      } catch (e) {
          log.error('Error saving record - ' + e.name, recordObject.type + ', Internal ID: ' + recordObject.id + ' - ' + e.message);
      }
  }

In this code example, we simply load the record and then save it. As a result, any User Event scripts we need to run on each record will be initiated. 

Conclusion

Once we begin to wrap our heads around it, the Map/Reduce script type isn’t so complex after all. In this example, we attempted to give the simplest use-case for a Map/Reduce script. In a future tutorial, we may dig a bit more into other important aspects of this incredible tool. If you would like to keep up with our latest development blogs, feel free to subscribe to our email list!

0
Would love your thoughts, please comment.x
()
x