Map/Reduce – Delete
Following from my previous post about deleting things from Netsuite (something that best practice dictates users should seldom do..) there is a third way of deleting object en-masse from Netsuite.
Map/Reduce scripts are designed to take a large dataset (i.e. a saved search) and perform instructions upon that data in small chunks. They are especially efficient over other script types (such as a scheduled script) as they have the ability to “yield” if any usage limits are reached; the yielded parts are re-processed without stopping the script.
I recently had a large dataset to remove in a Sandbox account due to some incorrect importing of data. I decided to re-use an existing MR script I had to accept a search of objects I wanted to remove and then delete them. This script had no specific checking (i.e. if there were dependencies preventing a delete) however the object I was deleting (contacts) had no activity so would not fail a delete.
Below is the code I used. It takes a search ID, which contained a filtered list of internalid’s, then for each ID deletes the object. At the point of deletion, it logs if the deletion was successful or not.
Furthermore, at the end of the script, it logs successes and failures into two CSV files which are output to the file cabinet
/**
* Accepts saved search of records and then deletes them. Stores success/fail information
* in CSV's saved in File Cabinet
*
* Version Name Date Notes
* 1.0 Slystor
*/
define(['N/search','N/record','N/runtime','N/file'],
function(search,record,runtime,file) {
/**
* Marks the beginning of the Map/Reduce process and generates input data.
*
* @typedef {Object} ObjectRef
* @property {number} id - Internal ID of the record instance
* @property {string} type - Record type id
*
* @return {Array|Object|Search|RecordRef} inputSummary
* @since 2015.1
*/
function getInputData() {
return search.load({
id: 'SEARCHIDGOESHERE'});
}
function map(context) {
try {
var strId = context.key;
var arrValue = context.value;
if(runtime.envType === 'SANDBOX') {
var delrec = record.delete({
type: 'contact',
id: strId,
});
}
if (delrec){
//convert single value to pairs, as the CSV function expects an object..
strId2 = {id:strId}
context.write({key:'Success',value:strId2});
}else{
//convert single value to pairs, as the CSV function expects an object..
strId2 = {id:strId}
context.write({key:'Fail',value:strId2});
}
}catch(e){
log.debug("error in map", e);
}
}
function summarize(summary) {
try{
var successDetails = [];
var errorDetails = [];
var type = summary.toString();
log.audit(type + ' Usage Consumed ', summary.usage + ' (10,000 limit per invocation)');
log.audit(type + ' Seconds ran for ', (summary.seconds / 10)+' minutes');
log.audit(type + ' Yields', summary.yields);
summary.output.iterator().each(function(key,value) {
if(key == 'Fail'){
var objfailureData = JSON.parse(value);
errorDetails.push(objfailureData);
}else if(key == 'Success'){
var objsuccessData = JSON.parse(value);
successDetails.push(objsuccessData);
}
return true;
});
if (successDetails.length > 0){
var str_csvfileinfo = prepare_csvfile(successDetails);
var int_randomno = Math.floor(Math.random() * 90000) + 10000;
var fileObj = file.create({name: 'success'+'_'+int_randomno+'.csv',fileType: file.Type.CSV,contents:str_csvfileinfo});
fileObj.folder = parseInt(FOLDERIDGOESHERE);
fileObj.encoding = file.Encoding.UTF_8;
//log.debug('fileObj',fileObj);
var file_Id = fileObj.save();
}
if (errorDetails.length > 0){
var str_csvfileinfo = prepare_csvfile(errorDetails);
var int_randomno = Math.floor(Math.random() * 90000) + 10000;
var fileObj = file.create({name: 'error'+'_'+int_randomno+'.csv',fileType: file.Type.CSV,contents:str_csvfileinfo});
fileObj.folder = parseInt(FOLDERIDGOESHERE);
fileObj.encoding = file.Encoding.UTF_8;
//log.debug('fileObj',fileObj);
var file_Id = fileObj.save();
}
}catch(e){
log.debug("error in summary",e);
}
}
return {
getInputData: getInputData,
map: map,
summarize: summarize
};
});
function prepare_csvfile(csvData){
var headerResult = {};
var result, ctr, keys, columnDelimiter, lineDelimiter, data;
var keysArray = [];
var headerResult, bodyResult;
data = csvData || null;
if (data == null || !data.length) {
return null;
}
columnDelimiter = csvData.columnDelimiter || ',';
lineDelimiter = csvData.lineDelimiter || '\n';
keys = Object.keys(csvData[0]);
for(var arr=0; arr<keys.length; arr++){
if(keys[arr] != 'Transaction Type'){
keysArray.push(keys[arr]);
}
}
csvData.forEach(function(item) {
ctr = 0;
var currKeys = Object.keys(item);
for(var p=0; p<currKeys.length; p++){
if((keysArray.indexOf(currKeys[p])) == -1){
if(currKeys[p] != 'Transaction Type'){
keysArray.push(currKeys[p]);
}
}
}
headerResult = keysArray;
keysArray.forEach(function(key) {
if(key != 'Transaction Type'){
if (ctr > 0)
bodyResult += columnDelimiter;
if(!(isEmpty(item[key]))){
if((isEmpty(bodyResult))){
bodyResult = '"' + item[key] + '"';
}else{
bodyResult += '"' + item[key] + '"';
}
}
ctr++;
}
});
bodyResult += lineDelimiter;
});
result = headerResult+lineDelimiter+bodyResult;
return result;
}
function isEmpty(stValue) {
return ((stValue === '' || stValue == null || stValue == undefined)
|| (stValue.constructor === Array && stValue.length == 0) || (stValue.constructor === Object && (function(
v) {
for ( var k in v)
return false;
return true;
})(stValue)));
}