Automate Closing Work Orders using Map/Reduce script 2.1

/**
 * @NApiVersion 2.1
 * @NScriptType MapReduceScript
 * @NModuleScope SameAccount
 */
 define(['N/search', 'N/record', './jj_common_library_sbsun762.js'],
 /**
* @param{format} format
* @param{record} record
* @param{search} search
* @param{commonLibrary} commonLibrary
*/
 (search, record, commonLibrary) => {        
     /**
      * Defines the function that is executed at the beginning of the map/reduce process and generates the input data.
      * @param {Object} inputContext
      * @param {boolean} inputContext.isRestarted - Indicates whether the current invocation of this function is the first
      *     invocation (if true, the current invocation is not the first invocation and this function has been restarted)
      * @param {Object} inputContext.ObjectRef - Object that references the input data
      * @typedef {Object} ObjectRef
      * @property {string|number} ObjectRef.id - Internal ID of the record instance that contains the input data
      * @property {string} ObjectRef.type - Type of the record instance that contains the input data
      * @returns {Array|Object|Search|ObjectRef|File|Query} The input data to use in the map/reduce process
      * @since 2015.2
      */
     const getInputData = (inputContext) => {
         try {
             let workOrderDetails = getWorkOrderDetails();
             log.debug("workOrderDetails",workOrderDetails);
             return workOrderDetails;
             
         } catch (e) {
             log.error('error@getInputData', e);
         }
     }


     /**
      * Defines the function that is executed when the reduce entry point is triggered. This entry point is triggered
      * automatically when the associated map stage is complete. This function is applied to each group in the provided context.
      * @param {Object} reduceContext - Data collection containing the groups to process in the reduce stage. This parameter is
      *     provided automatically based on the results of the map stage.
      * @param {Iterator} reduceContext.errors - Serialized errors that were thrown during previous attempts to execute the
      *     reduce function on the current group
      * @param {number} reduceContext.executionNo - Number of times the reduce function has been executed on the current group
      * @param {boolean} reduceContext.isRestarted - Indicates whether the current invocation of this function is the first
      *     invocation (if true, the current invocation is not the first invocation and this function has been restarted)
      * @param {string} reduceContext.key - Key to be processed during the reduce stage
      * @param {List<String>} reduceContext.values - All values associated with a unique key that was passed to the reduce stage
      *     for processing
      * @since 2015.2
      */
     const reduce = (reduceContext) => {
         try{
             let workOrderObj = JSON.parse(reduceContext.values);
             log.debug("workOrderObj",workOrderObj);
             let workOrderId = workOrderObj.InternalID.value;
             log.debug("workOrderId",workOrderId);
             let woCloseRec = record.transform({
                 fromType: record.Type.WORK_ORDER,
                 fromId: workOrderId,
                 toType: record.Type.WORK_ORDER_CLOSE,
                 isDynamic: true,
             });
             let id = woCloseRec.save({
                 enableSourcing: true,
                 ignoreMandatoryField: true
             });
            log.debug("transformed to work order close");
         }
         catch (e) {
             log.error('error@reduce', e);
         }
     }


     /**
      * Defines the function that is executed when the summarize entry point is triggered. This entry point is triggered
      * automatically when the associated reduce stage is complete. This function is applied to the entire result set.
      * @param {Object} summaryContext - Statistics about the execution of a map/reduce script
      * @param {number} summaryContext.concurrency - Maximum concurrency number when executing parallel tasks for the map/reduce
      *     script
      * @param {Date} summaryContext.dateCreated - The date and time when the map/reduce script began running
      * @param {boolean} summaryContext.isRestarted - Indicates whether the current invocation of this function is the first
      *     invocation (if true, the current invocation is not the first invocation and this function has been restarted)
      * @param {Iterator} summaryContext.output - Serialized keys and values that were saved as output during the reduce stage
      * @param {number} summaryContext.seconds - Total seconds elapsed when running the map/reduce script
      * @param {number} summaryContext.usage - Total number of governance usage units consumed when running the map/reduce
      *     script
      * @param {number} summaryContext.yields - Total number of yields when running the map/reduce script
      * @param {Object} summaryContext.inputSummary - Statistics about the input stage
      * @param {Object} summaryContext.mapSummary - Statistics about the map stage
      * @param {Object} summaryContext.reduceSummary - Statistics about the reduce stage
      * @since 2015.2
      */
     const summarize = (summaryContext) => {
         // Summarize added to find the Map/Reduce script excution end time.
     }


     /**
      * @description Fetch the work orders where variance is 0 and status is not closed.
      * @returns 
      */
     function getWorkOrderDetails(){
         try{
             let workOrderSearchObj = search.create({
             type: "workorder",
             filters:
             [
                 ["type","anyof","WorkOrd"], 
                 "AND", 
                 ["mainline","is","T"], 
                 "AND", 
                 ["formulanumeric: NVL({quantity} - {built}, 0) / NULLIF({quantity}, 0)","equalto","0"], 
                 "AND", 
                 ["status","noneof","WorkOrd:H"],
                 "AND", 
                 ["subsidiary","anyof","7","9","2"],
                 "AND", 
                 ["iswip","is","T"]
                //  "AND", 
                //  [[["internalidnumber","equalto","8269199"]],"OR",[["internalidnumber","equalto","111491374"]],"OR",[["internalidnumber","equalto","111491375"]]]


                ],
             columns:
             [
                 search.createColumn({name: "tranid", label: "DocumentNumber"}),
                 search.createColumn({name: "internalid", label: "InternalID"}),
                 search.createColumn({name: "quantity", label: "Quantity"}),
                 search.createColumn({name: "built", label: "Built"}),
                 search.createColumn({
                     name: "formulanumeric",
                     formula: "NVL({quantity} - {built}, 0) / NULLIF({quantity}, 0)",
                     label: "Variance"
                 })
             ]
             });
             let searchResultCount = workOrderSearchObj.runPaged().count;
             log.debug("workOrderSearchObj result count",searchResultCount);
             let workOrderDetailsObj = commonLibrary.iterateSavedSearch(workOrderSearchObj, commonLibrary.fetchSavedSearchColumn(workOrderSearchObj, 'label'));
             return workOrderDetailsObj;


         }
         catch ( Err )
         {
             log.debug( "Error @getWorkOrderDetails", Err );
             log.error( "Error @getWorkOrderDetails", Err )
         }
     }
     return { getInputData, reduce, summarize }
 });

/**
 * @NApiVersion 2.x
 * @NModuleScope SameAccount
 */
/************************************************************************************************
 * Description : This library file is used for the common functions
 ***********************************************************************************************/
define([], () => {


    /**
     * @description Global variable for storing errors ----> for debugging purposes
     * @type {Array.<Error>}Reference
     * @constant
     */
    const ERROR_STACK = [];


    /**
     * @description Check whether the given parameter argument has value on it or is it empty.
     * ie, To check whether a value exists in parameter
     * @param {*} parameter parameter which contains/references some values
     * @param {*} parameterName name of the parameter, not mandatory
     * @returns {Boolean} true if there exist a value else false
     */
    function checkForParameter(parameter, parameterName) {
        try {
            if (parameter !== "" && parameter !== null && parameter !== undefined && parameter !== false && parameter !== "null" && parameter !== "undefined" && parameter !== " " && parameter !== 'false' && parameter !== '[]' && parameter !== '{}') {
                return true;
            } else {
                if (parameterName) {
                    return false;
                }
            }
        } catch (e) {
            log.error({ title: "error@checkForParameter", details: e });
        }
    }


    /**
     * @description To assign a default value if the value argument is empty
     * @param {String|Number|Boolean|Object|Array|null|undefined} value
     * @param {String|Number|Boolean|Object|Array} defaultValue
     * @returns {*} either value or defaultValue
     */
    const assignDefaultValue = function assignDefaultValue(value, defaultValue) {
        try {
            if (checkForParameter(value)) {
                return value;
            } else {
                return defaultValue;
            }
        } catch (e) {
            log.error({ title: "error@assignDefaultValue", details: e });
        }
    }


    /**
     * @description To check whether the Given Value is a string or number
     * @param value
     * @returns {boolean} true if the given value is a string or number , else false
     */
    const isNumberOrString = (value) => {
        return (util.isString(value) || util.isNumber(value))
    };


    /**
     * @description Common Try-Catch function, applies to Object contains methods/function
     * @param {Object.<string,Function|any>} DATA_OBJ Object contains methods/function
     * @param {String} NAME  Name of the Object
     * @returns {void}
     */
    const applyTryCatch = function applyTryCatch(DATA_OBJ, NAME) {
        /**
         * @description  Try-Catch function
         * @param {Function} myfunction - reference to a function
         * @param {String} key - name of the function
         * @returns {Function|false}
         */
        const tryCatch = function (myfunction, key) {
            return function () {
                try {
                    return myfunction.apply(this, arguments);
                } catch (e) {
                    log.error("error in " + key, e);
                    ERROR_STACK.push(e);
                    return false;
                }
            };
        }
        for (let key in DATA_OBJ) {
            if (typeof DATA_OBJ[key] === "function") {
                DATA_OBJ[key] = tryCatch(DATA_OBJ[key], NAME + "." + key);
            }
        }
    }


    /**
    * COnvert the value to fixed decimals or Integer.
    * @param {*} value 
    * @returns 
    */
    function convertFloat(value, digits) {
        value = parseFloat(value);
        const formattedResult = value % 1 === 0 ? value.toString() : value.toFixed(digits);
        return Number(formattedResult);
    }


    /**
    * @description Object referencing NetSuite Saved Search
    * @typedef {Object} SearchObj
    * @property {Object[]} filters - Filters Array in Search
    * @property {Object[]} columns - Columns Array in Search
    */
    /**
    * @description to format Saved Search column to key-value pair where each key represents each columns in Saved Search
    * @param {SearchObj} savedSearchObj
    * @param {void|String} priorityKey
    * @returns {Object.<String,SearchObj.columns>}
    */
    function fetchSavedSearchColumn(savedSearchObj, priorityKey) {
        try {
            let columns = savedSearchObj.columns;
            let columnsData = {},
                columnName = '';
            columns.forEach(function (result) {
                columnName = '';
                if (result[priorityKey]) {
                    columnName += result[priorityKey];
                } else {
                    if (result.summary)
                        columnName += result.summary + '__';
                    if (result.formula)
                        columnName += result.formula + '__';
                    if (result.join)
                        columnName += result.join + '__';
                    columnName += result.name;
                }
                columnsData[columnName] = result;
            });
            return columnsData;
        } catch (e) {
            log.error({ title: 'error@fetchSavedSearchColumn', details: e });
        }
    }
    /**
     * @description Representing each result in Final Saved Search Format
     * @typedef formattedEachSearchResult
     * @type {{value:any,text:any}}
     */
    /**
     * @description to fetch and format the single saved search result. ie, Search result of a single row containing both text and value for each columns
     * @param {Object[]} searchResult contains search result of a single row
     * @param {Object.<String,SearchObj.columns>} columns
     * @returns {Object.<String,formattedEachSearchResult>|{}}
     */
    function formatSingleSavedSearchResult(searchResult, columns) {
        try {
            let responseObj = {};
            for (let column in columns) {
                responseObj[column] = {
                    value: searchResult.getValue(columns[column]),
                    text: searchResult.getText(columns[column])
                };
            }
            return responseObj;
        } catch (e) {
            log.error({ title: 'error@formatSingleSavedSearchResult', details: e });
        }
    }
    /**
     * @description to iterate over and initiate format of each saved search result
     * @param {SearchObj} searchObj
     * @param {void|Object.<String,SearchObj.columns>} columns
     * @returns {[]|Object[]}
     */
    function iterateSavedSearch(searchObj, columns) {
        try {
            if (!checkForParameter(searchObj)) {
                return false;
            }


            if (!checkForParameter(columns)) {
                columns = fetchSavedSearchColumn(searchObj);
            }


            let response = [];
            let searchPageRanges;
            try {
                searchPageRanges = searchObj.runPaged({
                    pageSize: 1000
                });
            } catch (err) {
                return [];
            }
            if (searchPageRanges.pageRanges.length < 1) {
                return [];
            }


            let pageRangeLength = searchPageRanges.pageRanges.length;


            for (let pageIndex = 0; pageIndex < pageRangeLength; pageIndex++) {
                searchPageRanges.fetch({
                    index: pageIndex
                }).data.forEach(function (result) {
                    response.push(formatSingleSavedSearchResult(result, columns));
                });
            }
            return response;
        } catch (e) {
            log.error({ title: 'error@iterateSavedSearch', details: e });
        }
    }


    /**
    * @description function for handling the escape characters
    * @param unsafe Data
    * @return {*}
    */
    function escapeXml(unsafe) {
        try {
            if (unsafe) {
                return unsafe.replace(/[<>&'"]/g, function (c) {
                    switch (c) {
                        case '<':
                            return '&lt;';
                        case '>':
                            return '&gt;';
                        case '&':
                            return '&amp;';
                        case ''':
                            return '&apos;';
                        case '"':
                            return '&quot;';
                    }
                });
            }


        } catch (e) {
            log.error({ title: 'error@escapeXml', details: e });
        }
    }


    return {
        checkForParameter,
        assignDefaultValue,
        isNumberOrString,
        applyTryCatch,
        convertFloat,
        fetchSavedSearchColumn,
        iterateSavedSearch,
        escapeXml
    };
});

Leave a comment

Your email address will not be published. Required fields are marked *