Transferring Quantity Pricing from One Item to Another Item using SuiteScript

/**
 * @NApiVersion 2.1
 * @NScriptType MapReduceScript
 */
define([
    'N/record',
    'N/search'
], (record, search) => {

    /**
     * Function finds the item type and returns the record.load object
     * @param {String} id 
     * @returns record.load
     */
    function loadItemRecord(id) {
        const ITEM_TYPE = {
            'Assembly': 'assemblyitem',
            'Description': 'descriptionitem',
            'Discount': 'discountitem',
            'GiftCert': 'giftcertificateitem',
            'InvtPart': 'inventoryitem',
            'Group': 'itemgroup',
            'Kit': 'kititem',
            'Markup': 'markupitem',
            'NonInvtPart': 'noninventoryitem',
            'OthCharge': 'otherchargeitem',
            'Payment': 'paymentitem',
            'Service': 'serviceitem',
            'Subtotal': 'subtotalitem'
        };
        try {
            return record.load({
                type: ITEM_TYPE[search.lookupFields({
                    type: 'item',
                    id: id,
                    columns: ['type']
                }).type[0].value],
                id: id,
                isDynamic: true
            });
        } catch (error) {
            log.debug('ERROR - loadItem failed with error: id:' + id, e.message)
        };
    };

    /**
     * Get the Body field values from the Item record
     * @param {Object} itemRecLoad 
     * @returns itemBodyFieldsArray
     */
    const getItemBodyFields = (itemRecLoad) => {
        try {
            let itemBodyFields = itemRecLoad.getFields();
            let filteredItemFields = itemBodyFields.filter(field => field.startsWith('price'));
            let itemBodyFieldsArray = [];
            filteredItemFields.forEach(element => {
                itemBodyFieldsArray.push({
                    fieldId: element,
                    value: itemRecLoad.getValue({ fieldId: element })
                });
            });

            return itemBodyFieldsArray;
        } catch (error) {
            log.error('Error in getItemPriceBodyFields', error);
            return [];
        }
    }

    /**
     * Get the sublist field values from the Item Price Level
     * @param {Object} itemRecLoad 
     * @returns sublistFieldObj
     */
    const getItemSublistFields = (itemRecLoad) => {
        try {
            let itemSublists = itemRecLoad.getSublists();
            let filteredItemSublists = itemSublists.filter(sublist => sublist.startsWith('price'));
            let sublistFieldObj = {}
            filteredItemSublists.forEach(sublist => {
                sublistFieldObj[sublist] = [];
                let sublistFields = itemRecLoad.getSublistFields({ sublistId: sublist });
                let filteredSublistFields = sublistFields.filter(sublistField => sublistField.startsWith('price_'));
                for (let i = 0; i < itemRecLoad.getLineCount(sublist); i++) {
                    let obj = {};
                    filteredSublistFields.forEach(sublistField => {
                        obj[sublistField] = itemRecLoad.getSublistValue({
                            sublistId: sublist,
                            line: i,
                            fieldId: sublistField
                        });
                    })
                    obj['line'] = i;
                    sublistFieldObj[sublist].push(obj)
                }
            });

            return sublistFieldObj;
        } catch (error) {
            log.error('Error in getItemSublistValues', error);
            return {};
        }
    }

    /**
     * Set the Body field values on the Item record.
     * @param {Array} headerValues 
     * @param {Object} newItemRec 
     */
    const setBodyFields = (headerValues, newItemRec) => {
        try {
            headerValues.forEach(element => {
                newItemRec.setValue({
                    fieldId: element.fieldId,
                    value: element.value
                });
            })
        } catch (error) {
            log.error('Error in setBodyFields', error)
        }
    }

    /**
     * Set the sublist field values on the Item Price Level
     * @param {Object} SublistValues 
     * @param {Object} newItemRec 
     */
    const setSublistFields = (SublistValues, newItemRec) => {
        try {
            for (sublist in SublistValues) {
                SublistValues[sublist].forEach(element => {
                    for (let key in element) {
                        if (key == 'line') { continue };
                        newItemRec.selectLine({ sublistId: sublist, line: element['line'] })
                        newItemRec.setCurrentSublistValue({
                            sublistId: sublist,
                            fieldId: key,
                            value: element[key]
                        });
                    }
                    newItemRec.commitLine({ sublistId: sublist });
                })
            }
        } catch (error) {
            log.error('Error in setSublistFields', error);
        }
    }

    /**
     * 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 {
            log.error('GET INPUT DATA STARTS');

            let itemSearch = search.create({
                type: "assemblyitem",
                filters:
                    [
                        ["custitem_jj_link_ex_item", "noneof", "@NONE@"],
                        "AND",
                        ["type", "anyof", "Assembly"],
                        "AND",
                        ["isinactive", "is", "F"],
                        "AND",
                        ["externalidstring", "startswith", "EXTASBMAT"],
                        "AND",
                        ["custitem_jj_price_level_updated", "is", "F"],

                    ],
                columns:
                    [
                        search.createColumn({ name: "custitem_jj_link_ex_item", label: "Link of Ex Item" })
                    ]
            });

            let itemResultSet = itemSearch.run().getRange({
                start: 0,
                end: 1000
            });

            return itemResultSet;
        } catch (error) {
            log.error('Error in getInputData', error);
            return [];
        }
    }

    /**
     * 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) => {
        let newItemId = null;
        try {
            let reduceValue = JSON.parse(reduceContext.values);
            newItemId = reduceValue.id;
            log.debug('newItemId', newItemId);
            let newItemRec = loadItemRecord(newItemId);
            let exItemId = newItemRec.getValue('custitem_jj_link_ex_item');
            let oldItemRec = loadItemRecord(exItemId);
            let itemBodyFieldValues = getItemBodyFields(oldItemRec);
            log.debug('itemBodyFieldValues', itemBodyFieldValues);
            let itemSublistFieldValues = getItemSublistFields(oldItemRec);
            log.debug('itemSublistFieldValues', itemSublistFieldValues);
            setBodyFields(itemBodyFieldValues, newItemRec);
            setSublistFields(itemSublistFieldValues, newItemRec);
            newItemRec.setValue({ fieldId: 'custitem_jj_price_level_updated', value: true });
            let recordUpdated = newItemRec.save({
              ignoreMandatoryFields: true
            });
            log.debug('Record is Updated', recordUpdated)
        } catch (error) {
            log.error('Error in reduce', error);
            reduceContext.write({
                key: 'error',
                value: { itemId: newItem, error: error.message }
            });
        }
    }

    /**
     * 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) => {
        try {
            var titleArray = ["Item ID", "Error Description"];
            var csvFileData = titleArray.toString() + 'rn';
            let flag = 0, fileID = "";

            summaryContext.output.iterator().each(function (key, value) {
                if (key === 'error') {
                    let parseSummary = JSON.parse(value);
                    log.error("error parse", parseSummary)
                    flag = flag + 1;
                    let itemId = parseSummary['itemId'];
                    csvFileData += itemId + ',' + parseSummary.error.replace(',', " ") + 'rn';
                }

                return true;
            });
            if (flag > 0) {
                let fileObj = file.create({
                    name: 'ERROR-File Created' + '-' + Math.floor(Date.now() / 1000) + '.csv',
                    fileType: file.Type.CSV,
                    folder: '8471369',
                    contents: csvFileData
                });

                fileID = fileObj.save()
            }
            log.debug('MAP REDUCE COMPLETED');
        } catch (error) {
            log.error('Error in summarize', error);
        }
    }

    return { getInputData, reduce, summarize }
});

Leave a comment

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