I have to inactivate 1.7 million item records with...
# suitescript
f
I have to inactivate 1.7 million item records within Netsuite, I've created this Map/Reduce script which works but it's fairly slow. Is their any way of inactivating items faster ?
Copy code
* @NApiVersion 2.x
 * @NScriptType MapReduceScript
 */
define(['N/search', 'N/record', 'N/log'], function (search, record, log) {

    /**
     * Retrieves Inventory Items that meet the specified inactivation criteria.
     */
    function getInputData() {
        return search.create({
            type: search.Type.INVENTORY_ITEM,
            filters: [
                ["type", "anyof", "InvtPart"],
                "AND",
                ["transaction.type", "anyof", "@NONE@"],
                "AND",
                ["locationquantityonhand", "isempty", ""],
                "AND",
               ["created","within","3/24/2024 12:00 am","3/31/2024 11:59 pm"]
            ],
            columns: [
                'internalid',
                'itemid'
            ]
        });
    }

    /**
     * Inactivates each Inventory Item retrieved by the search.
     */
    function map(context) {
        try {
            var result = JSON.parse(context.value);
            var itemId = result.id;

            var itemRecord = record.load({
                type: record.Type.INVENTORY_ITEM,
                id: itemId,
                isDynamic: false
            });

            itemRecord.setValue({
                fieldId: 'isinactive',
                value: true
            });

            var savedId = itemRecord.save();

            log.debug({
                title: 'Item Inactivated',
                details: 'Item ID: ' + itemId + ', Saved As: ' + savedId
            });

        } catch (e) {
            log.error({
                title: 'Error Inactivating Item',
                details: e.message || JSON.stringify(e)
            });
        }
    }

    return {
        getInputData: getInputData,
        map: map
    };

});
e
Use reduce instead of map and up your concurrency. Map isn't guaranteed to run in parallel and reduce is. In map, create batches of items to be inactivated and pass those to reduce.
👍 2
f
Thanks so much for this I appreciate the feedback 🙂
m
Seems like a CSV import would be faster than a script. You can also turn off UE scripts and workflows for a CSV import to speed it up. Using the "Use Multi-threading" option would help performance as well. The biggest downfall of this is breaking your list up into 25k row files, but you could script the creation of these files as well since this sounds like a one-time task.
1
t
Just 68 CSV records, NBD.
😆 2
s
can you use submitFields instead of the load and save?
f
Well after deliberate investigation, I've decidedto go through the CSV import route and created a script for that. The Map/Reduce script regardless was taking too long to process the data even with the Get Input Stage. thanks for the suggestions
j
When I had to do something similar (doing an update on over 1mill records) what I did was this: 1) create CSV of all records to be updated 2) write a shells script to split my CSV into reasonably sized files 3) upload files into file cabinet in a “to be updated” folder 4) write scheduled map reduce where the getInput() does this: • grabs file from “to be updated” folder • gets contents of the file, dumps into object • moves file into a “done” folder then the reduce iterates through the records and does the update. Made a few deployments, kicked it off, boom, done overnight.
👍 2
n
There is another route using m/r you could call a SuiteLet with batches of ID's, using promises you could call it asynchronously multiple times per map stage.
s
where is it documented that map isn't parallel but reduce is? I thought both those stages were treated the same when it comes to concurrency?
e
f
thanks @jen I took your route 🙂
Copy code
/**
 * @NApiVersion 2.x
 * @NScriptType MapReduceScript
 */
define(['N/file', 'N/search', 'N/record', 'N/log'], function(file, search, record, log) {

    const SOURCE_FOLDER_ID = 65164;
    const DONE_FOLDER_ID = 65165;

    function getInputData() {
        var allItemIds = [];

        var fileSearchResults = search.create({
            type: 'file',
            filters: [
                ['folder', 'anyof', SOURCE_FOLDER_ID],
                'AND',
                ['filetype', 'is', 'CSV']
            ],
            columns: ['internalid']
        }).run().getRange({ start: 0, end: 1000 });

        for (var i = 0; i < fileSearchResults.length; i++) {
            var fileId = fileSearchResults[i].getValue({ name: 'internalid' });

            try {
                var fileObj = file.load({ id: fileId });
                var contents = fileObj.getContents();
                var lines = contents.split(/\r?\n/);

                for (var j = 1; j < lines.length; j++) { // Skip header
                    var line = lines[j].trim();
                    if (!line) continue;

                    var internalId = line.split(',')[0].trim();
                    if (internalId && !isNaN(internalId)) {
                        allItemIds.push(internalId);
                    } else {
                        log.audit('Skipped Line', 'Line ' + (j + 1) + ' is invalid: "' + line + '"');
                    }
                }

                // Move processed file to Done folder
                fileObj.folder = DONE_FOLDER_ID;
                fileObj.save();

                log.audit('File Processed', 'Processed file ID: ' + fileId + ' and moved to Done folder.');

            } catch (fileError) {
                log.error('File Load Failed', 'Could not process file ID ' + fileId + ': ' + fileError.message);
            }
        }

        return allItemIds;
    }

    function map(context) {
        var itemId = context.value;

        try {
            record.submitFields({
                type: record.Type.INVENTORY_ITEM,
                id: itemId,
                values: {
                    isinactive: true
                },
                options: {
                    enableSourcing: false,
                    ignoreMandatoryFields: true
                }
            });
            log.audit('Item Inactivated', 'Item ID ' + itemId + ' set to inactive.');
        } catch (e) {
            log.error('Error Inactivating Item', 'Item ID ' + itemId + ': ' + e.message);
        }
    }

    return {
        getInputData: getInputData,
        map: map
    };
});
s
@ehcanadian that suiteanswer uses the same word can for both the
map
and
reduce
text at the beginning of the article?
e
@Shawn Talbert I know I've read it somewhere, just don't recall exactly where, it's just stuck with me. They say can and may in the docs all over the place lol
s
indeed, NS documentation is rather infamous