/**
* @NApiVersion 2.1
* @NScriptType MapReduceScript
*/
define(['N/error', 'N/config', 'N/record', 'N/runtime', 'N/search', 'N/https', '../Utility/JJ Shopify NS Utility.js', '../Common Library/JJ Shopify Common Library.js'],
/**
* @param{error} error
* @param{record} record
* @param{runtime} runtime
* @param{search} search
*/
(config, error, record, runtime, search, https, jjUtil, shopifyLib) => {
const shopifyLibrary = shopifyLib.Library;
/**
* 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 STATIC_SHOPIFY_CUSTOMER = 152810;
var STATIC_SHIPPING_METHOD = 7194;
const ORDER_STATUS = 'B';
const ORDER_DISCOUNT = 7154;
const FEDEX_GROUND = 2241;
const FEDEX_TWODAY = 4481;
const FEDEX_OVERNIGHT = 2239;
/**
* @description the function to check whether a value exists in parameter
* @param parameter -passing parameter
* @param parameterName - passing parameter name
* @returns{Boolean}
*/
function checkForParameter(parameter, parameterName) {
if (
parameter != "" &&
parameter != null &&
parameter != undefined &&
parameter != "null" &&
parameter != "undefined" &&
parameter != " " &&
parameter != false
) {
return true;
} else {
if (parameterName)
log.debug(
"Empty Value found",
"Empty Value for parameter " + parameterName
);
return false;
}
}
/**
*
* @param lineItems
* @returns {boolean|*}
*/
function processItems(lineItems, shopifyRecordId) {
var itemArray = [];
var itemIDObj;
for (var item_index = 0, item_len = lineItems.length; item_index < item_len; item_index++) {
itemArray.push(lineItems[item_index].sku);
log.debug("itemArray", itemArray);
}
itemIDObj = fetchItemID(itemArray);
log.debug("itemIDObj", itemIDObj);
for (var item_index = 0, item_len = lineItems.length; item_index < item_len; item_index++) {
var index = findIndexInData(itemIDObj, itemIDObj.length, lineItems[item_index].sku);
log.debug("index", index);
log.debug(" line item index", lineItems[item_index].sku);
// var itemRec= record.load({
// type:record.Type.INVENTORY_ITEM,
// id: NetSuiteID
// })
if (index >= 0) {
if (checkForParameter(itemIDObj[index].internalID)) {
lineItems[item_index]["NetSuiteID"] = itemIDObj[index].internalID;
}
} else {
lineItems[item_index]["NetSuiteID"] = " ";
try {
record.submitFields({
type: "customrecord_cl_shopify_integ_cl_468",
id: shopifyRecordId,
values: {
custrecord_cl_err_so_create: "Item record does not found in the Netsuite or item doesnt configured with Ecom pricelevel"
},
options: {enableSourcing: true, ignoreMandatoryFields: true},
});
} catch (err) {
log.debug("Error on item does not exist field submission", err);
}
}
}
return lineItems;
}
/**
*
* @param data
* @param length
* @param value
* @returns {number}
*/
function findIndexInData(data, length, value) {
var result = -1;
for (var i = 0; i < length; i++) {
if (data[i].ItemSku == value) {
result = i;
}
}
return result;
}
/**
*
* @param itemArray
* @returns {*[]}
*/
function fetchItemID(itemArray) {
var lineItems = itemArray;
var filteredIDs = [];
for (var i = 0; i < lineItems.length; i++) {
filteredIDs.push(['itemid', 'is', lineItems[i]])
filteredIDs.push('OR');
}
filteredIDs.pop();
log.debug("Filter ID", filteredIDs);
if (itemArray.length > 0) {
var itemSearchObj = search.create({
type: 'item',
filters: [
[filteredIDs],
"AND",
["type", "anyof", "InvtPart"],
"AND",
["pricing.pricelevel","anyof","458"]
// ["formulatext: {itemid}", "contains", ...itemArray]
],
columns: [
search.createColumn({name: "internalid", label: "Internal ID"}),
search.createColumn({
name: "formulatext",
formula: "ltrim(regexp_substr({name},'[^:]*$'))",
label: "Formula (Text)"
}),
search.createColumn({name: "displayname", label: "Display Name"}),
search.createColumn({
name: "pricelevel",
join: "pricing",
label: "Price Level"
})
],
});
var result = itemSearchObj.run();
var itemIDObj = {};
//log.debug('itemIDObj', itemIDObj);
var resultCount = itemSearchObj.runPaged().count;
log.debug("itemSearchObj", itemSearchObj);
log.debug("resultCount", resultCount);
var itemArr = [];
if (resultCount > 0) {
itemSearchObj.run().each(function (result) {
var internalID = result.getValue({name: "internalid"});
var ItemSku = result.getValue({
name: "formulatext",
formula: "ltrim(regexp_substr({name},'[^:]*$'))",
label: "Formula (Text)"
});
var priceLevel =result.getValue({
name: "pricelevel",
join: "pricing",
label: "Price Level"
})
log.debug("Price level",priceLevel)
itemIDObj = {
"internalID": internalID,
"ItemSku": ItemSku
}
itemArr.push(itemIDObj);
return true;
});
} else {
itemIDObj = {
"internalID": " ",
"ItemSku": " "
}
//itemArr.push(itemIDObj);
log.debug('itemIDObj', itemIDObj);
}
log.debug("resultCount", resultCount);
return itemArr;
}
}
/**
* @description Function to search the sales order already existing
* @param salesorderId
* @returns {boolean|*}
*/
function checkSalesOrder(salesorderId) {
try {
let salesorderSearchObj = search.create({
type: "transaction",
filters:
[
["type", "anyof", "SalesOrd", "CashSale"],
"AND",
["mainline", "is", "T"],
"AND",
["custbody_cl_shopify_order_id", "is", salesorderId.toString().trim()]
],
columns:
[
search.createColumn({name: "internalid", label: "Internal ID"}),
search.createColumn({name: "custbody_cl_shopify_order_id", label: "salesorderId"}),
search.createColumn({name: "tranid", label: "Document Number"})
]
});
let searchResultCount = salesorderSearchObj.runPaged().count;
log.debug("salesorderSearchObj", salesorderSearchObj);
log.debug("salesorder search result count", searchResultCount);
if (searchResultCount === 0)
return false;
else
return salesorderId;
} catch (e) {
log.debug('error @ checkSalesOrder', e);
}
}
/**
*
* @param salesorderId
* @param dataObj
*/
function createSalesOrder(salesorderId, dataObj, shopifyRecordId) {
log.debug("Sales order details", dataObj)
try {
//create sales order record
var objRecord = record.create({
type: record.Type.SALES_ORDER,
isDynamic: true
});
objRecord.setValue({
fieldId: "otherrefnum",
value: dataObj.name
});
objRecord.setValue({
fieldId: "entity",
value: STATIC_SHOPIFY_CUSTOMER,
});
objRecord.setValue({
fieldId: "custbody_cl_shopify_order_id",
value: dataObj.id.toString(),
});
objRecord.setValue({
fieldId: "orderstatus",
value: ORDER_STATUS,
});
objRecord.setValue({
fieldId: "discountitem",
value: ORDER_DISCOUNT,
});
//
objRecord.setValue({
fieldId: "discountrate",
value: dataObj.current_total_discounts * -1,
});
// //current_total_tax dataObj
objRecord.setValue({
fieldId: "memo",
value: dataObj.note,
});
if (dataObj.shipping_lines.length !== 0) {
//FedEx Ground if shipping amount between $0-$6
// FedEx 2 day if shipping amount is $18.90
// FedEx overnight if shipping amount is $30.90
var fedexprice = parseFloat(dataObj.shipping_lines[0].price);
if (fedexprice > 0 && fedexprice < 7) {
STATIC_SHIPPING_METHOD = FEDEX_GROUND;
} else if (fedexprice == 18.90) {
STATIC_SHIPPING_METHOD = FEDEX_TWODAY;
} else if (fedexprice == 30.90) {
STATIC_SHIPPING_METHOD = FEDEX_OVERNIGHT;
}
objRecord.setValue({
fieldId: "shipmethod",
value: STATIC_SHIPPING_METHOD,//static shopify shipping method
});
objRecord.setValue({
fieldId: "shippingcost",
value: dataObj.shipping_lines[0].price,
});
log.debug("Shipping cost", dataObj.shipping_lines[0].price)
} else {
objRecord.setValue({
fieldId: "shipmethod",
value: STATIC_SHIPPING_METHOD
});
objRecord.setValue({
fieldId: "shippingcost",
value: 0.00
});
}
/**
* @description To check whether the Given Value is a string or number
* @param value
* @returns {boolean} true if the given value is a string or number , else false
*/
const isNumberOrString = (value) => {
return (util.isString(value) || util.isNumber(value))
};
let shipping_address = util.isObject(dataObj.shipping_address) ? dataObj.shipping_address : ""
log.debug("shipping_address", shipping_address)
//Shipping Address
let shippingFields = [];
if (jjUtil.checkForParameter(shipping_address)) {
shippingFields = [
{
fieldId: 'country',
value: isNumberOrString(shipping_address.country_code) ? shipping_address.country_code : ""
},
{
fieldId: 'attention',
value: ""
},
{
fieldId: 'addressee',
value: isNumberOrString(shipping_address.name) ? shipping_address.name : ""
},
{
fieldId: 'addrphone',
value: isNumberOrString(shipping_address.phone) ? shipping_address.phone : ""
},
{
fieldId: 'addr1',
value: isNumberOrString(shipping_address.address1) ? shipping_address.address1 : ""
},
{
fieldId: 'addr2',
value: isNumberOrString(shipping_address.address2) ? shipping_address.address2 : ""
},
{
fieldId: 'addr3',
value: ""
},
{
fieldId: 'city',
value: isNumberOrString(shipping_address.city) ? shipping_address.city : ""
},
{
fieldId: 'state',
value: isNumberOrString(shipping_address.province) ? shipping_address.province : ""
},
{
fieldId: 'zip',
value: isNumberOrString(shipping_address.zip) ? shipping_address.zip : ""
}]
}
let shipAddressRecord = objRecord.getSubrecord({
fieldId: 'shippingaddress'
});
//Set Address Sublist Subrecord values
for (let subrecordField of shippingFields) {
if (subrecordField.value !== null && subrecordField.value !== undefined && subrecordField.value !== "") {
try {
shipAddressRecord.setValue({
fieldId: subrecordField.fieldId,
value: subrecordField.value
});
} catch (er) {
log.error('error@ setting shipping Address Fields skipped', JSON.stringify(er));
continue;
}
}
}
let billing_address = util.isObject(dataObj.billing_address) ? dataObj.billing_address : ""
log.debug("billing_address", billing_address)
//Billing Address
let billingFields = []
if (jjUtil.checkForParameter(billing_address)) {
billingFields = [
{
fieldId: 'country',
value: isNumberOrString(billing_address.country_code) ? billing_address.country_code : ""
},
{
fieldId: 'attention',
value: ""
},
{
fieldId: 'addressee',
value: isNumberOrString(billing_address.name) ? billing_address.name : ""
},
{
fieldId: 'addrphone',
value: isNumberOrString(billing_address.phone) ? billing_address.phone : ""
},
{
fieldId: 'addr1',
value: isNumberOrString(billing_address.address1) ? billing_address.address1 : ""
},
{
fieldId: 'addr2',
value: isNumberOrString(billing_address.address2) ? billing_address.address2 : ""
},
{
fieldId: 'addr3',
value: ""
},
{
fieldId: 'city',
value: isNumberOrString(billing_address.city) ? billing_address.city : ""
},
{
fieldId: 'state',
value: isNumberOrString(billing_address.province) ? billing_address.province : ""
},
{
fieldId: 'zip',
value: isNumberOrString(billing_address.zip) ? billing_address.zip : ""
}
]
}
let billAddressRecord = objRecord.getSubrecord({
fieldId: 'billingaddress'
});
//Set Address Sublist Subrecord values
for (let subrecordField2 of billingFields) {
if (subrecordField2.value !== null && subrecordField2.value !== undefined && subrecordField2.value !== "") {
try {
billAddressRecord.setValue({
fieldId: subrecordField2.fieldId,
value: subrecordField2.value
});
} catch (er) {
log.error('error@ setting billing Address Fields skipped', JSON.stringify(er));
continue;
}
}
}
let order_line = util.isArray(dataObj.line_items) ? dataObj.line_items : ""
var lineItems = dataObj.line_items;
for (var item_index = 0; item_index < lineItems.length; item_index++) {
if (checkForParameter(lineItems[item_index]["NetSuiteID"])) {
try {
log.debug("lineItems", lineItems[item_index]["NetSuiteID"])
objRecord.selectNewLine({
sublistId: "item",
});
objRecord.setCurrentSublistValue({
sublistId: "item",
fieldId: "item",
value: parseInt(lineItems[item_index]["NetSuiteID"]),
ignoreFieldChange: false,
});
// objRecord.setCurrentSublistValue({
// sublistId: "item",
// fieldId: "price",
// value:"458",
// ignoreFieldChange: false,
// });
// objRecord.setCurrentSublistValue({
// sublistId: "item",
// fieldId: "taxcode",
// value:"4872",
// ignoreFieldChange: false,
// });
objRecord.setCurrentSublistValue({
sublistId: "item",
fieldId: "quantity",
value: parseInt(lineItems[item_index].quantity),
ignoreFieldChange: false,
});
objRecord.setCurrentSublistValue({
sublistId: "item",
fieldId: "grossamt",
value: Number(lineItems[item_index].total) + Number(lineItems[item_index].total_tax),
ignoreFieldChange: false,
});
objRecord.setCurrentSublistValue({
sublistId: "item",
fieldId: "custcol_shopify_line_id",
value: lineItems[item_index].id.toString(),
ignoreFieldChange: false,
});
objRecord.commitLine({
sublistId: "item",
});
}catch (err) {
log.debug("skipped item line", err);
}
}
}
var soRecord = objRecord.save({
enableSourcing: true,
ignoreMandatoryFields: true
});
if (soRecord) {
//
// var objRecordLoad = record.load({
// type: record.Type.SALES_ORDER,
// id: soRecord,
// isDynamic: true,
// });
//
// var lineCount= objRecordLoad.getLineCount({
//
// sublistId:'item',
// })
// log.debug("lineCount", lineCount)
//
// for(var k=0; k<lineCount; k++){
//
// var lineNum = objRecordLoad.selectLine({
// sublistId: 'item',
// line: k
// });
// objRecordLoad.setCurrentSublistText({
// sublistId: "item",
// fieldId: "price",
// text:"Ecomm",
// ignoreFieldChange: false,
// });
//
// objRecordLoad.commitLine({
// sublistId: "item",
// });
//
//
// }
// objRecordLoad.save({
// enableSourcing: true,
// ignoreMandatoryFields: true
// });
//
record.submitFields({
type: "customrecord_cl_shopify_integ_cl_468",
id: shopifyRecordId,
values: {
custrecord_cl_sales_order: soRecord,
custrecord_cl_customer: STATIC_SHOPIFY_CUSTOMER,
custrecord_cl_err_so_create: "",
custrecord_cl_partial_order: (order_line.length == objRecord.getLineCount({sublistId: 'item'})) ? false : true,
},
options: {enableSourcing: true, ignoreMandatoryFields: true}
});
var configRec=record.load({
type:"customrecord_cl_shopify_integ_cl_468",
id:shopifyRecordId
});
log.debug("configRec", configRec)
var isPartial=configRec.getValue({
fieldId:'custrecord_cl_partial_order',
});
log.debug("isPartial", isPartial)
if (isPartial==true){
let errorMessage = {status: "ERROR", error: { message: "Item price level is not EComm OR Item is not present in Netsuite OR Item is not Inventory Item"}}
log.debug("errorMessage", errorMessage)
record.submitFields({
type: "customrecord_cl_shopify_integ_cl_468",
id: shopifyRecordId,
values: {
custrecord_cl_err_so_create: JSON.stringify(errorMessage)
},
options: {enableSourcing: true, ignoreMandatoryFields: true}
});
}
}
return soRecord;
} catch (e) {
log.error('error @ createSalesOrder', e);
record.submitFields({
type: "customrecord_cl_shopify_integ_cl_468",
id: shopifyRecordId,
values: {
custrecord_cl_err_so_create: JSON.stringify(e)
},
options: {enableSourcing: true, ignoreMandatoryFields: true}
});
return false;
}
}
/**
* @description To store the Request on custom record as well as further processing outcome
* @param OrderAPIRequest - The JSON Response from Shopify
*/
function shopifyIntegrationInstance(OrderAPIRequest) {
let shopifyRecordId;
try {
shopifyRecordId = fetchShopifyRecordByOrderID(OrderAPIRequest.id)
if (!checkForParameter(shopifyRecordId)) {
let shopifyIntegrationRecord = record.create({
type: "customrecord_cl_shopify_integ_cl_468",
isDynamic: true
});
shopifyIntegrationRecord.setValue({
fieldId: "custrecord_cl_order_number",
value: OrderAPIRequest.name.toString().trim()
});
shopifyIntegrationRecord.setValue({
fieldId: "name",
value: OrderAPIRequest.id.toString().trim()
});
shopifyIntegrationRecord.setValue({
fieldId: "custrecord_cl_order_api",
value: JSON.stringify(OrderAPIRequest)
});
shopifyRecordId = shopifyIntegrationRecord.save({ignoreMandatoryFields: true});
}
return shopifyRecordId;
} catch (err) {
log.error('error@shopifyIntegrationInstance', err);
}
}
/**
* @description the saved search for finding whether the shopify integration record is already created for the shopify order
* @param shopifyOrderID - the shopify record id
* @returns {*}
*/
function fetchShopifyRecordByOrderID(shopifyOrderID) {
let customrecord_cl_shopify_integ_cl_468SearchObj = search.create({
type: "customrecord_cl_shopify_integ_cl_468",
filters:
[
["name", "is", shopifyOrderID.toString().trim()],
"AND",
["isinactive", "is", "F"]
],
columns:
[
search.createColumn({name: "internalid", label: "InternalID"})
]
});
let searchResultCount = customrecord_cl_shopify_integ_cl_468SearchObj.runPaged().count;
log.debug("customrecord_cl_shopify_integ_cl_468SearchObj result count", searchResultCount);
let shopifyRecordID;
customrecord_cl_shopify_integ_cl_468SearchObj.run().each(function (result) {
shopifyRecordID = result.getValue({
name: "internalid",
label: "InternalID"
})
//return true;
});
return shopifyRecordID
}
/**
* @description Functions from N/config module encapsulated
* @type {{isProduction(): boolean}}
*/
const configFunction = {
/**
* To know whether the account is Production Environment or not
* @returns {boolean} true when account is Production, false in all other scenarios
*/
isProduction() {
var companyInfo = config.load({
type: config.Type.COMPANY_INFORMATION
});
var ns_companyid = companyInfo.getValue({
fieldId: 'companyid'
}).toString().trim().toLowerCase();
if (Number.isNaN(Number(ns_companyid))) //Will be NaN for Sandbox or Release Preview accounts
return true; // return false when script moved to production
return true;
}
};
const getInputData = (inputContext) => {
try {
// If current executing environment is not Production, then disregard the entire process
if (!shopifyLibrary.isProduction())
return log.debug('*** NOT EXECUTED ***', '*** NOT PRODUCTION ENVIRONMENT ***'), false;
log.debug('*** START ***', '*** MAP REDUCE SCRIPT STARTS ***');
let currentDate = new Date();
currentDate.setUTCHours(currentDate.getUTCHours() - 4);
let updated_at_min = currentDate.toISOString();
log.debug("updated_at_min", updated_at_min);
let apiCredentials = shopifyLibrary.SHOPIFY_API_REQUESTS.GET_LISTOFORDERS;
apiCredentials = apiCredentials + '&updated_at_min=' + updated_at_min;
let BODY_TO_SEND = "";
let shopifyAPIResponse = shopifyLibrary.requestShopify(
apiCredentials,
"GET",
BODY_TO_SEND
);
log.debug("shopifyAPIResponse", shopifyAPIResponse.orders.length);
//log.debug("shopify",shopifyAPIResponse.orders);
return shopifyAPIResponse.orders;
} catch (e) {
log.debug("error@getInput", e);
return JSON.stringify({status: "FAILURE", reason: "ERROR", error: {name: e.name, message: e.message}});
}
}
/**
* 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 {
var dataObj = JSON.parse(reduceContext.values[0]);
log.debug("Data Objects", dataObj);
var salesorderId = dataObj.id;
log.debug("salesorderId", salesorderId);
var checkSalesOrderId = checkSalesOrder(salesorderId)
log.debug("check", checkSalesOrderId);
if (checkSalesOrderId) {
log.debug("salesorder already exists");
return true;
}
let shopifyRecordId = shopifyIntegrationInstance(dataObj);
log.debug("shopifyRecordId", shopifyRecordId)
dataObj.line_items = processItems(
dataObj.line_items,
shopifyRecordId
);
log.debug("processItems", dataObj.line_items);
var newSalesorder = createSalesOrder(salesorderId, dataObj, shopifyRecordId);
log.debug("newSalesorder", newSalesorder);
if (newSalesorder) {
let apiCredentials = shopifyLibrary.SHOPIFY_API_REQUESTS.UPDATE_ORDER;
apiCredentials = apiCredentials.replace("{order_id}", dataObj.id);
let BODY_TO_SEND = {
"order": {
"id": dataObj.id,
"tags": "Synced To NetSuite"
}
}
let shopifyAPIResponse = shopifyLibrary.requestShopify(
apiCredentials,
"PUT",
BODY_TO_SEND
);
// log.debug("shopifyAPIResponse", shopifyAPIResponse)
} else {
let apiCredentials = shopifyLibrary.SHOPIFY_API_REQUESTS.UPDATE_ORDER;
apiCredentials = apiCredentials.replace("{order_id}", dataObj.id);
let BODY_TO_SEND = {
"order": {
"id": dataObj.id,
"tags": "NetSuite Sync Unsuccessful"
}
}
let shopifyAPIResponse = shopifyLibrary.requestShopify(
apiCredentials,
"PUT",
BODY_TO_SEND
);
}
} catch (e) {
log.error("error@reduce", e);
}
}
return {getInputData, reduce}
});