define(['N/error', 'N/file', 'N/https', 'N/search', 'N/url', 'N/runtime'],
/**
* @param{error} error
* @param{file} file
* @param{http} https
* @param{search} search
* @param{url} url
* @param {runtime} runtime
*/
(error, file, https, search, url, runtime) => {
"use strict";
let pageSize = 1000;
/**
* Different stores IDs from Vusion manager
*/
const STORE_IDS = {
"CONCORD": "airporthomeappliance_us.concord1505",
"DUBLIN": "airporthomeappliance_us.dublin6842",
"EMERYVILLE": "airporthomeappliance_us.emeryville5815",
"HAYWARD": "airporthomeappliance_us.hayward3525",
"REDWOODCITY": "airporthomeappliance_us.redwoodcity2424",
"ROSEVILLE": "airporthomeappliance_us.roseville1251",
"SANJOSE": "airporthomeappliance_us.sanjose966",
"SANRAFAEL": "airporthomeappliance_us.sanrafael777",
};
/**
* Inventory location IDs
*/
const INVENTORY_LOCATIONS = {
"PRODUCT_DISTRIBUTION_CENTER": "26"
};
/**
* The status of items that needs to be send
*/
const ITEM_STATUS = {
"A": "1",
"B": "2",
"O": "5"
};
/**
* function to post csv file
* @param file
* @returns {*}
* Returns response of API call
*/
function accessDetails(fileContent) {
try {
let responseArray = [];
let headers = [];
headers['Content-Type'] = "application/octet-stream";
headers['Ocp-Apim-Subscription-Key'] = "a607c5f78d3748269797004007728360";
if (checkForParameter(fileContent)) {
for (let key in STORE_IDS) {
log.debug("key", STORE_IDS[key])
let response = https.post({
url: `https://api-us.vusion.io/vcloud/v1/stores/${STORE_IDS[key]}/items/files/Inventory_Item_Details.csv`,
headers: headers,
body: fileContent
});
log.debug("response", response)
responseArray.push(response)
}
}
return responseArray
} catch (e) {
log.error("error@accessDetails", e);
return [];
}
}
/**
* To check whether a value exists in parameter
* @param parameter
* @param parameterName
* @returns {boolean}
*/
function checkForParameter(parameter, parameterName) {
if (parameter !== "" && parameter !== null && parameter !== undefined && parameter !== false && parameter !== "null" && parameter !== "undefined" && parameter !== " " && parameter !== 'false') {
return true;
} else {
if (parameterName)
log.debug('Empty Value found', 'Empty Value for parameter ' + parameterName);
return false;
}
}
/**
* to fetch and format the single saved search result. ie, Search result of a single row containing both text and value for each columns.
* to format the contents as required
* @param searchResult
* @param columns
* @returns {{}}
* Returns response object
*/
function formatSingleSavedSearchResult(searchResult, columns) {
let responseObj = {};
for (let column in columns) {
if ((column == "Sales Description") || (column == "Name")) {
let resVal = searchResult.getValue(columns[column]).replace(/,/g, "|");// Replaced comma (,) with (|) to avoid column seperation
let resValTrim = resVal.trim();
responseObj[column] = resValTrim.replace(/[nr]+/g, " ");
} else if ((column == "Brand") || (column == "Item Status")) {
responseObj[column] = searchResult.getText(columns[column]).replace(/,/g, "|");// Replaced comma (,) with (|) to avoid column seperation
} else if (column == "MSRP") {
responseObj[column] = searchResult.getValue(columns[column]).replace(/,/g, "");// Replaced comma (,) with empty for amount to avoid column seperation
} else {
responseObj[column] = searchResult.getValue(columns[column]);
}
}
return responseObj;
}
/**
* @description to iterate over and initiate format of each saved search result
* @param {Object} param
* @param {SearchObj} param.searchObj
* @param {void|Object.<String,SearchObj.columns>} param.columns
* @param {number} param.PAGE_INDEX
* @param {number} param.PAGE_SIZE
* @returns {[]|Object[]}
*/
function iterateSavedSearch(searchObj, columns, PAGE_INDEX, PAGE_SIZE) {
if (!checkForParameter(searchObj))
return false;
if (!checkForParameter(columns))
columns = fetchSavedSearchColumn(searchObj);
let response = [];
let searchPageRanges;
try {
searchPageRanges = searchObj.runPaged({
pageSize: Number.isInteger(Number(PAGE_SIZE)) ? parseInt(Number(PAGE_SIZE)) : 500 //Default Page Size
});
} catch (err) {
return Number.isInteger(PAGE_INDEX) ? {
pageInfo: {
pageLength: 1,
pageIndex: 1,
isLastPage: true
},
lines: []
} : [];
}
if (searchPageRanges.pageRanges.length < 1)
return Number.isInteger(PAGE_INDEX) ? {
pageInfo: {
pageLength: 1,
pageIndex: 1,
isLastPage: true
},
lines: []
} : [];
let pageRangeLength = searchPageRanges.pageRanges.length;
//log.debug('pageRangeLength', pageRangeLength);
//To make sure the pageIndex has minimum value of one and maximum value of pageRangeLength
const pageIndexRangeRectifier = function (value, pageRange) {
//log.debug("value pagrng", value + ' ' + pageRange)
if (!Number.isInteger(Number(value)))
return 1;
if ((Number(value) - 1) <= 0)
return 1;
if ((Number(value) - 1) >= Number(pageRange))
return Number(pageRange);
return Number(value);
};
if (Number.isInteger(PAGE_INDEX))
searchPageRanges.fetch({
index: pageIndexRangeRectifier(PAGE_INDEX, pageRangeLength) - 1
}).data.forEach(function (result) {
response.push(formatSingleSavedSearchResult(result, columns, true));
});
else
for (let pageIndex = 0; pageIndex < pageRangeLength; pageIndex++)
searchPageRanges.fetch({
index: pageIndex
}).data.forEach(function (result) {
response.push(formatSingleSavedSearchResult(result, columns));
});
return Number.isInteger(PAGE_INDEX) ? {
pageInfo: {
pageLength: pageRangeLength,
pageIndex: Number(pageIndexRangeRectifier(PAGE_INDEX, pageRangeLength)),
isLastPage: Number(pageIndexRangeRectifier(PAGE_INDEX, pageRangeLength)) >= Number(pageRangeLength) ? true : false
},
lines: response
} : response;
}
/**
* @description to format Saved Search column to key-value pair where each key represents each columns in Saved Search
* @param {Search} savedSearchObj
* @param {void|String} priorityKey
* @returns {Object.<String,SearchObj.columns>}
*/
function fetchSavedSearchColumn(savedSearchObj, priorityKey) {
let columns = savedSearchObj.columns;
let columnsData = {}, columnName = '';
columns.forEach((result, counter) => {
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 + '__' + counter?.toString();
}
columnsData[columnName] = result;
});
return columnsData;
}
/**
* function to create item saved search
* @returns {*[]}
* Returns saved search result after iteration
*/
function itemSavedSearch() {
try {
let arrFinal = [];
let inventoryitemSearchObj = search.create({
type: "inventoryitem",
filters:
[
["type", "anyof", "InvtPart"],
"AND",
["totalquantityonhand", "greaterthan", "0"],
"AND",
["isinactive", "is", "F"],
"AND",
["inventorylocation", "anyof", INVENTORY_LOCATIONS.PRODUCT_DISTRIBUTION_CENTER],
"AND",
["custitem_aha_item_status", "anyof", Object.values(ITEM_STATUS)]
],
columns:
[
search.createColumn({
name: "itemid",
sort: search.Sort.ASC,
label: "Name"
}),
search.createColumn({ name: "custitem_jj_strdigtt_ahap2213", label: "Sales Description" }),
search.createColumn({ name: "custitem_aha_sales_price", label: "Sales Price" }),
search.createColumn({ name: "custitem42", label: "Sales Margin %" }),
search.createColumn({ name: "locationquantityavailable", label: "Available" }),
search.createColumn({ name: "totalquantityonhand", label: "Total Quantity On Hand" }),
search.createColumn({ name: "cseg_aha_brand", label: "Brand" }),
search.createColumn({ name: "custitem39_2", label: "Rebate Available" }),
search.createColumn({ name: "custitem_aha_item_status", label: "Item Status" }),
search.createColumn({ name: "custitem_msrp", label: "MSRP" }),
search.createColumn({ name: "custitem37", label: "PTS" }),
search.createColumn({
name: "formulatext",
formula: "{custitem1}",
label: "Website URL"
}),
search.createColumn({ name: "custitem28", label: "Depth (IN)" }),
search.createColumn({ name: "custitem29", label: "Width (IN)" }),
search.createColumn({ name: "custitem30", label: "Height (IN)" })
]
});
let searchResultCount = inventoryitemSearchObj.runPaged().count;
let searchPageCount = Number.isInteger(Number(searchResultCount)) ? Number(Math.ceil(searchResultCount / pageSize)) : 0;
for (let i = 1; i <= searchPageCount; i++) {
let arrResult = iterateSavedSearch(inventoryitemSearchObj, fetchSavedSearchColumn(inventoryitemSearchObj, 'label'), i, pageSize).lines
arrFinal = arrFinal.concat(arrResult);
//log.debug('arrResult', arrResult.length)
}
return arrFinal;
} catch (e) {
log.error("error@itemSavedSearch", e);
return []
}
}
/**
* function to create open box saved search
*/
const openBoxItemSearch = () => {
try {
let arrFinal = [];
let customrecord_jj_open_box_item_log1108SearchObj = search.create({
type: "customrecord_jj_open_box_item_log1108",
filters:
[
["custrecord_jj_open_box_display", "isnotempty", ""],
"AND",
["isinactive", "is", "F"]
],
columns:
[
search.createColumn({ name: "custrecord_jj_open_box_display", label: "Name" }),
// search.createColumn({ name: "custrecord_jj_descrip_openboxlog1149", label: "Sales Description" }),
search.createColumn({
name: "custitem_jj_strdigtt_ahap2213",
join: "CUSTRECORD_JJ_ITEM_NAME_OPENBOXLOG1149",
label: "Sales Description"
}),
search.createColumn({ name: "custrecord_jj_sales_price_openboxlog1149", label: "Sales Price" }),
search.createColumn({
name: "formulatext",
formula: "'N/A'",
label: "Sales Margin %"
}),
search.createColumn({
name: "formulatext",
formula: "'1'",
label: "Available"
}),
search.createColumn({
name: "formulatext",
formula: "'1'",
label: "Total Quantity On Hand"
}),
search.createColumn({ name: "custrecord_jj_brand_openboxlog_1149", label: "Brand" }),
search.createColumn({
name: "formulatext",
formula: "'FALSE'",
label: "Rebate Available"
}),
search.createColumn({ name: "custrecord_jj_status_openboxlog1149", label: "Item Status" }),
search.createColumn({
name: "custitem_msrp",
join: "CUSTRECORD_JJ_ITEM_NAME_OPENBOXLOG1149",
label: "MSRP"
}),
search.createColumn({
name: "formulatext",
formula: "'FALSE'",
label: "PTS"
}),
search.createColumn({
name: "formulatext",
formula: "{CUSTRECORD_JJ_ITEM_NAME_OPENBOXLOG1149.custitem1}",
label: "Website URL"
}),
search.createColumn({
name: "custitem_jj_website_depth_in_ahap1235",
join: "CUSTRECORD_JJ_ITEM_NAME_OPENBOXLOG1149",
label: "Depth (IN)"
}),
search.createColumn({
name: "custitem_jj_website_width_ahap1235",
join: "CUSTRECORD_JJ_ITEM_NAME_OPENBOXLOG1149",
label: "Width (IN)"
}),
search.createColumn({
name: "custitem_jj_website_height_ahap1235",
join: "CUSTRECORD_JJ_ITEM_NAME_OPENBOXLOG1149",
label: "Height (IN)"
}),
]
});
let searchResultCount = customrecord_jj_open_box_item_log1108SearchObj.runPaged().count;
log.debug("customrecord_jj_open_box_item_log1108SearchObj result count", searchResultCount);
let searchPageCount = Number.isInteger(Number(searchResultCount)) ? Number(Math.ceil(searchResultCount / pageSize)) : 0;
for (let i = 1; i <= searchPageCount; i++) {
let arrResult = iterateSavedSearch(customrecord_jj_open_box_item_log1108SearchObj, fetchSavedSearchColumn(customrecord_jj_open_box_item_log1108SearchObj, 'label'), i, pageSize).lines
arrFinal = arrFinal.concat(arrResult);
//log.debug('arrResult', arrResult.length)
}
return arrFinal;
} catch (err) {
log.error("error@openBoxItemSearch", err)
}
}
/**
* function to create CSV content
* @param {*} arrayContent
* @returns
*/
const createCSV = (arrayContent) => {
try {
let csvContent = "Name" + ',' + "Sales Description" + ',' + "Sales Price" + ',' + "Sales Margin %" + ',' + "Available" + ',' + "Total Quantity On Hand" + ',' + "Brand" + ',' + "Rebate Available" + ',' + "Item Status" + ',' + "MSRP" + ',' + "PTS" + ',' + "Website URL" + "," + "Depth (IN)" + "," + "Width (IN)" + "," + "Height (IN)n";
for (let i = 0; i < arrayContent.length; i++) {
for (let key of Object.keys(arrayContent[i])) {
csvContent += arrayContent[i][key];
if (key != "Height (IN)") {
csvContent += ','
}
}
csvContent += 'n'
}
return csvContent;
} catch (err) {
log.error("error@createCSV", err);
return [];
}
}
/**
* 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 {
if (runtime.envType == runtime.EnvType.PRODUCTION) {// checking the environment
let invSavedSearch = itemSavedSearch();
// log.debug(invSavedSearch.length + ' invSavedSearch', invSavedSearch);
let openBoxDetails = openBoxItemSearch();
// log.debug("openBoxDetails", openBoxDetails);
let sendData = invSavedSearch.concat(openBoxDetails);
let csvContent = createCSV(sendData);
// log.debug('csvContent', csvContent);
let response = accessDetails(csvContent);
log.debug('response', response);
return response;
}
} 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 reducedValue = reduceContext.values[0];
} catch (e) {
log.error("error@reduce", e)
}
}
return { getInputData, reduce }
});