/**
* @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 }
});