Scenario:
The purpose of this development is to detail the integration plan between NetSuite and Magento through REST APIs, with a specific focus on synchronizing inventory items from NetSuite to Magento.
Here, the inventory item sync involves; product sync, category sync, custom attribute sync, image sync, related item sync and special price sync.
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
/*******************************************************************************************************
* Szco Supplies, Inc
*
* SZCO-422 : NetSuite - Magento Product Sync
*
* *****************************************************************************************************
*
* Author: Jobin and Jismi IT Services
*
* Date Created : 29-June-2023
*
*Description : This script is to implement an integration between NetSuite and Magento to sync the
products in NetSuite to Magento
*
* REVISION HISTORY
*
* @version 1.0 SZCO-422 : 29-June-2023 : Created the initial build by JJ0152
*
* Revision 1.1 ${03-July-2023} Swathi Krishna K.S: Updated - Image Sync
* Revision 1.2 ${22-August-2023} Swathi Krishna K.S Updated - Stock Sync
*
******************************************************************************************************/
define(['N/https', 'N/search', 'N/file', 'N/record', 'N/runtime', '../Common Library/jj_adobe_common_library.js', '../Common Library/jj_adobe_ns_utility.js'],
/**
* @param{https} https
* @param{log} log
* @param{record} record
* @param{runtime} runtime
*/
(https, search, file, record, runtime, magentolib, jjutil) => {
"use strict"
const ATTRIBUTE_SET_ID = 4;//default attribute set id in Adobe commerce
let domain;//Netsuite domain url
//Netsuite - Magento item status mapping
const STATUSMAPPING = {
5: 11,
7: 13,
3: 9,
6: 12,
1: 8,
4: 10
};
const ITEMSTATUSES = ["1", "3", "4", "7", 1, 3, 4, 7];
/**
* Check whether the account is production or not.
* @returns {boolean}
*/
function isProduction() {
try {
let ns_companyid = runtime.accountId.toString().trim().toLowerCase();
let accountId = ns_companyid.replace('_', '-');
//set domain url
domain = `https://${accountId}.app.netsuite.com`;
if (!Number.isNaN(Number(ns_companyid))) //Will be NaN for Sandbox or Release Preview accounts
return true; // todo** return true when script moved to production
return false;
} catch (e) {
log.error("Error @ isProduction", e.message);
}
}
/**
* Defines the function definition that is executed before record is loaded.
* @param {Object} scriptContext
* @param {Record} scriptContext.newRecord - New record
* @param {string} scriptContext.type - Trigger type; use values from the context.UserEventType enum
* @param {Form} scriptContext.form - Current form
* @param {ServletRequest} scriptContext.request - HTTP request information sent from the browser for a client action only.
* @since 2015.2
*/
const beforeLoad = (scriptContext) => {
try {
let recordNew = scriptContext.newRecord;
if ((scriptContext.type === "copy")) {
recordNew.setValue({ fieldId: 'custitem_jj_adobe_item_id', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_magento_sync_error', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_magento_sync_status', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_magento_sync_lastupdated', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image1_', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image2_', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image3_', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image4_', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image5_', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image1_id', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image2_id', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image3_id', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image4_id', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image5_id', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image_id', value: "" });
recordNew.setValue({ fieldId: 'custitem_jj_image6_id', value: "" });
}
} catch (e) {
log.error("error@beforeload", e.message);
}
}
/**
* Defines the function definition that is executed before record is submitted.
* @param {Object} scriptContext
* @param {Record} scriptContext.newRecord - New record
* @param {Record} scriptContext.oldRecord - Old record
* @param {string} scriptContext.type - Trigger type; use values from the context.UserEventType enum
* @since 2015.2
*/
const beforeSubmit = (scriptContext) => {
//The integration should work for only inventory item type
if ((scriptContext?.oldRecord?.type || scriptContext?.newRecord?.type) != 'inventoryitem') {
return;
}
//try catch is not given since we need to throw errors as part of validations
/********Validations- start***************/
if (runtime.executionContext != 'USERINTERFACE') {
return;
}
if (scriptContext.type == 'create' || scriptContext.type == 'edit' || scriptContext.type == 'xedit') {
let recObj = scriptContext.newRecord;
let name = recObj.getValue({ fieldId: 'storedisplayname' });
let itemStatus = recObj.getValue({ fieldId: 'custitem10' });
let showInWebsite = recObj.getValue({ fieldId: "isonline" });
let price;
price = recObj.getSublistValue({ //price* todo
sublistId: 'price1',
fieldId: 'price_1_',
line: 0//list price line
});
if (!name && ITEMSTATUSES.includes(itemStatus) && showInWebsite) {
throw "The Adobe Commerce Name is required as you are going to sync it to Adobe. Please provide a store display name!";
} else if (!price && String(price) != "0" && ITEMSTATUSES.includes(itemStatus) && showInWebsite) {
throw "The 'List Price' is required as you are going to sync it to Adobe. Please provide a List price for this item!";
}
}
/********Validations-end***************/
}
/**
* Defines the function definition that is executed after record is submitted.
* @param {Object} scriptContext
* @param {Record} scriptContext.newRecord - New record
* @param {Record} scriptContext.oldRecord - Old record
* @param {string} scriptContext.type - Trigger type; use values from the context.UserEventType enum
* @since 2015.2
*/
const afterSubmit = (scriptContext) => {
/*Check whether the environment is production or not
Work only if it is production*/
if (isProduction()) {
/* DELETE ITEM SYNC TO MAGENTO - Start*/
let oldRec = scriptContext.oldRecord;
let newRec = scriptContext.newRecord;
try {
if (scriptContext.type === 'delete') {
let magentoId = oldRec.getValue({ fieldId: 'custitem_jj_adobe_item_id' });
let sku = oldRec.getValue({ fieldId: 'itemid' });
if (magentoId) {
let deleteApi = magentolib.ADOBE_API_REQUESTS.DELETE_PRODUCT;
deleteApi = deleteApi.replace('{sku}', sku);
magentolib.commonFunctions.sendAPIRequest(
"DELETE",
deleteApi
);
}
}
} catch (e) {
log.error('error@delete', e);
}
/* DELETE ITEM SYNC TO MAGENTO - End*/
try {
if ((scriptContext.type === 'edit') || (scriptContext.type === 'xedit') || (scriptContext.type === 'create')) {
let recType = newRec.type;
let objRecord = record.load({
type: recType,
id: scriptContext.newRecord.id,
isDynamic: true,
});
/* ADOBE - MAGENTO ITEM UPDATE - start */
if ((scriptContext.type === 'edit') || (scriptContext.type === 'xedit')) {
let returnVal = updateItem(scriptContext, objRecord, oldRec, newRec);
if (returnVal) {
return true;
}
}
/* ADOBE - MAGENTO ITEM UPDATE - end */
/* ADOBE - MAGENTO ITEM CREATE - start */
if ((scriptContext.type === 'create') || (scriptContext.type === 'edit') || (scriptContext.type === 'xedit')) {
let returnVal = createItem(scriptContext, objRecord, oldRec, newRec);
if (returnVal) {
return true;
}
}
/* ADOBE - MAGENTO ITEM CREATE - end */
/* STOCK UPDATE - Start*/
stockUpdate(scriptContext, 'create')
/* STOCK UPDATE - End*/
}
} catch (e) {
log.error("error@aftersubmit", e);
}
}
}
/**
* Create the item in Adobe commerce
* @param {Object} scriptContext
* @param {Record} objRecord
* @param {Record} oldRec
* @param {Record} newRec
* @returns {boolean}
*/
function createItem(scriptContext, objRecord, oldRec, newRec) {
try {
let customAttributes = [];
let errorNew = objRecord.getValue({ fieldId: 'custitem_jj_magento_sync_error' });
//set the error log field to empty
if (errorNew) {
objRecord.setValue({
fieldId: 'custitem_jj_magento_sync_error',
value: null
});
}
//check whether item is already created or not.
if ((scriptContext.type === 'edit') || (scriptContext.type === 'xedit')) {
let magentoIdOld = oldRec.getValue({ fieldId: 'custitem_jj_adobe_item_id' });
//Don't create item again if it already exisits
if (magentoIdOld) {
log.debug("Item already created in netsuite,exiting");
return true;
}
}
let syncMagento = objRecord.getValue({ fieldId: 'custitem10' });
let showInWebsite = objRecord.getValue({ fieldId: 'isonline' });
//Integrate the items only if the 'item status' is any of New - In Production or Stock inventory item.
if (ITEMSTATUSES.includes(syncMagento) && showInWebsite) {
let sku = objRecord.getValue({ fieldId: 'itemid' });
let name = objRecord.getValue({ fieldId: 'storedisplayname' });
let inactive = objRecord.getValue({ fieldId: 'isinactive' });
let category = objRecord.getValue({ fieldId: 'custitem_jj_magento_catogories_' });
let weight = objRecord.getValue({ fieldId: 'weight' });
let weightUnit = objRecord.getValue({ fieldId: 'weightunit' });
//convert weight unit to lb
if (weight) {
weight = convertToPounds(parseInt(weight), weightUnit).toFixed(2)
}
let includePromotionPrice = objRecord.getValue({ fieldId: 'custitem20' });
let nonDiscountable = objRecord.getValue({ fieldId: 'custitem22' });
let isPromotion = objRecord.getValue({ fieldId: 'custitem20' });
let promoLineNum = objRecord.findSublistLineWithValue({
sublistId: 'price1',
fieldId: 'pricelevel',
value: 12//purchase Price
});
let promotionPrice = objRecord.getSublistValue({
sublistId: 'price1',
fieldId: 'price_1_',
line: promoLineNum
});
//change*
let lineNumber = objRecord.findSublistLineWithValue({
sublistId: 'price1',
fieldId: 'pricelevel',
value: 1//List Price
});
let priceIs = 0;
if (lineNumber >= 0) {
//The base price value
priceIs = objRecord.getSublistValue({
sublistId: 'price1',
fieldId: 'price_1_',
line: lineNumber
});
}
//Adding different price levels with the item
let tierPrices = setPriceLevels(objRecord, includePromotionPrice, nonDiscountable);
//Adding the category
let categoryLinks = setCategory("create", customAttributes, category);
//set all additional attributes
customAttributes = commonSetAttrFun(objRecord, customAttributes);
//Adding Link Products(related item sublist)
let linkProducts = getlinkProduct(objRecord, sku);
// creating one image object to send with create product API request image*
let mediaEntries = [];
let currentTime = Math.round(+new Date() / 1000);
let imageFileId = objRecord.getValue({ fieldId: 'custitem_atlas_item_image' });//todo change field
if (imageFileId) {
let fileObj = file.load({
id: imageFileId
});
let imageFileContent = fileObj.getContents();
let curImageType = jjutil.savedSearch.fetchImageType(fileObj.fileType);
let curImageName = `image_${currentTime}.${curImageType}`;
//let curImageName1 = "new_image1." + curImageType;
let imageType = "image/" + curImageType;
let imageObj = {
"id": 0,
"media_type": "image",
"label": "Image",
"position": 0,
"disabled": false,
"types": [
"thumbnail",
"small_image",
"image"
],
"content": {
"base64_encoded_data": imageFileContent,
"type": imageType,
"name": curImageName
}
};
mediaEntries.push(imageObj);
} //image* end
//product payload
let itemdetails =
{
"product": {
"sku": sku,
"name": name,
"visibility": 4,
"attribute_set_id": ATTRIBUTE_SET_ID,
"price": parseFloat(priceIs),
"status": (inactive || (categoryLinks.length == 0)) ? 2 : 1,//if inactive, disable the item in magento 2-disable , 1- active
"type_id": 'simple',
"extension_attributes": {
"website_ids": [
1
],
"stock_item": {
"qty": 0,
"is_in_stock": true
},
"category_links": categoryLinks
},
"media_gallery_entries": mediaEntries,
// image*
"custom_attributes": customAttributes,
"tier_prices": tierPrices,
"product_links": linkProducts
}
};
if (weight) {
itemdetails.product.weight = weight;
}
//Before creating an item in Magento, check if an item with same SKU exists in Magento.
let inventoryitemSearchObj = search.create({
type: "inventoryitem",
filters:
[
["type", "anyof", "InvtPart"],
"AND",
["nameinternal", "is", sku],
"AND",
["custitem_jj_adobe_item_id", "isnotempty", ""]
],
columns:
[
search.createColumn({ name: "internalid", label: "Internal ID" })
]
});
let searchResultCount = inventoryitemSearchObj.runPaged().count;
if (searchResultCount > 0) {
objRecord.setValue({
fieldId: "custitem_jj_magento_sync_error",
value: "The product sync failed because an item with the same SKU exists in Magento."
});
objRecord.setValue({ fieldId: "custitem_jj_magento_sync_status", value: 'Failed' });
}
else {
// Api to create item in magento
let apiCredentials = magentolib.ADOBE_API_REQUESTS.CREATE_PRODUCT;
//API call to create item in Adobe commerce
let Response = magentolib.commonFunctions.sendAPIRequest(
"POST",
apiCredentials,
itemdetails
);
let responsebody = Response.body;
let responsecode = Response.code;
//on sucess
if (responsecode == 200 || responsecode == 204 || responsecode == 201) {
let magentoid = JSON.parse(responsebody).id;
let response = JSON.parse(responsebody);
if (response.media_gallery_entries[0]) {
objRecord.setValue({
fieldId: 'custitem_jj_image_id',
value: response.media_gallery_entries[0].id
});//todo change field
}
objRecord.setValue({
fieldId: "custitem_jj_adobe_item_id",
value: magentoid
});
objRecord.setValue({
fieldId: "custitem_jj_magento_sync_error",
value: ''
});
objRecord.setValue({
fieldId: "custitem_jj_magento_sync_lastupdated",
value: new Date()
});
objRecord.setValue({ fieldId: "custitem_jj_magento_sync_status", value: 'Created' });
}
//on failure
else {
objRecord.setValue({
fieldId: "custitem_jj_magento_sync_error",
value: (JSON.parse(responsebody).message == 'The Product with the "%1" SKU doesn' + "'t" + ' exist.') ? "The item failed to sync with Magento because one or more related items are not synced with Magento. Please sync the related items first" : JSON.parse(responsebody).message
});
objRecord.setValue({ fieldId: "custitem_jj_magento_sync_status", value: 'Failed' });
}
//Special prize sync
specialPrizeSync(isPromotion, promotionPrice, sku)
// create image object to attach extra images to the product
itemImageSync(scriptContext, objRecord, newRec, oldRec, sku, 'create');
}
objRecord.save({
ignoreMandatoryFields: true,
enableSourcing: false
});
}
} catch (e) {
log.error("Error @ Create Item", e.message);
}
}
/**
* Update the item in Adobe commerce
* @param {Object} scriptContext
* @param {Record} objRecord
* @param {Record} oldRec
* @param {Record} newRec
* @returns {boolean}
*/
function updateItem(scriptContext, objRecord, oldRec, newRec) {
try {
let customAttributes = [];
let magentoIdOld, syncToMagento, showInWebsite, errorNew;
magentoIdOld = objRecord.getValue({ fieldId: 'custitem_jj_adobe_item_id' });
syncToMagento = objRecord.getValue({ fieldId: 'custitem10' });
showInWebsite = objRecord.getValue({ fieldId: "isonline" }); //change*
errorNew = objRecord.getValue({ fieldId: 'custitem_jj_magento_sync_error' });
//If the error status exists in old record clear it in on save.
if (errorNew) {
objRecord.setValue({
fieldId: "custitem_jj_magento_sync_error",
value: null
});
}
//Verify the item already created or not, if not do creation first
if (ITEMSTATUSES.includes(syncToMagento) && magentoIdOld && showInWebsite) {
let nameOld = oldRec.getValue({ fieldId: 'storedisplayname' });
let nameNew = objRecord.getValue({ fieldId: 'storedisplayname' });
let inactive = objRecord.getValue({ fieldId: 'isinactive' });
let sku = objRecord.getValue({ fieldId: 'itemid' });
let weightNew = objRecord.getValue({ fieldId: 'weight' });
let weightNewUnit = objRecord.getValue({ fieldId: 'weightunit' }); //"*****"
//convert weight unit to lb
if (weightNew) {
weightNew = convertToPounds(parseInt(weightNew), weightNewUnit).toFixed(2)
}
let categoryOld = oldRec.getValue({ fieldId: 'custitem_jj_magento_catogories_' });
let categoryNew = objRecord.getValue({ fieldId: 'custitem_jj_magento_catogories_' });
let magentoId = oldRec.getValue({ fieldId: "custitem_jj_adobe_item_id" });
let name = nameNew ? nameNew : nameOld;
let includePromotionPrice = objRecord.getValue({ fieldId: 'custitem20' });
let nonDiscountable = objRecord.getValue({ fieldId: 'custitem22' });
let isPromotion = newRec.getValue({ fieldId: 'custitem20' });
let promoLineNum = newRec.findSublistLineWithValue({
sublistId: 'price1',
fieldId: 'pricelevel',
value: 12//purchase Price
});
let promotionPrice = newRec.getSublistValue({
sublistId: 'price1',
fieldId: 'price_1_',
line: promoLineNum
});
//get the promotion price in old record context
let promotionPriceOld = oldRec.getSublistValue({
sublistId: 'price1',
fieldId: 'price_1_',
line: promoLineNum
});
let lineNumber = objRecord.findSublistLineWithValue({
sublistId: 'price1',
fieldId: 'pricelevel',
value: 1//List Price
});
let priceIs = 0;
if (lineNumber >= 0) {
//The base price value
priceIs = objRecord.getSublistValue({
sublistId: 'price1',
fieldId: 'price_1_',
line: lineNumber
});
}
//The item object
let itemdetails =
{
"product": {
"id": magentoId,
"sku": sku,
"name": name,
"price": parseFloat(priceIs),
"status": inactive ? 2 : 1,//if inactive or sync is off, disable the item in magento 2-disable , 1- active
"type_id": 'simple',
"extension_attributes": {
"website_ids": [
1
],
"stock_item": {
"qty": 0,
"is_in_stock": true
}
}
}
};
if (weightNew) {
itemdetails.product.weight = weightNew;
}
//Adding different price levels - price*
let tierPrices;
tierPrices = setPriceLevels(objRecord, includePromotionPrice, nonDiscountable);
//check to see the tier_price is true, if it is true push that to itemdedails
if (tierPrices.length) {
itemdetails.product["tier_prices"] = tierPrices;
}
//Adding the Category
let categoryLinks = setCategory("update", customAttributes, categoryNew, categoryOld, sku);
if (categoryLinks.length > 0) {
itemdetails.product.extension_attributes["category_links"] = categoryLinks;
}
else {
itemdetails.product["status"] = 2;
}
//set all additional attributes
customAttributes = commonSetAttrFun(objRecord, customAttributes);
// adding custom attribute values to the payload
if (customAttributes.length > 0) {
itemdetails.product["custom_attributes"] = customAttributes;
}
//Adding Link Products(related item sublist)
let linkProducts = getlinkProduct(objRecord, sku);
itemdetails.product["product_links"] = linkProducts;
//Call the update API to update the item
let productUpdateApi = magentolib.ADOBE_API_REQUESTS.UPDATE_PRODUCT;
productUpdateApi = productUpdateApi.replace('{sku}', sku);
let productUpdateResponse = magentolib.commonFunctions.sendAPIRequest(
"PUT",
productUpdateApi,
itemdetails
);
//set on success
if (productUpdateResponse.code === 200 || productUpdateResponse.code === 204 || productUpdateResponse.code === 201) {
objRecord.setValue({
fieldId: "custitem_jj_magento_sync_error",
value: ''
});
objRecord.setValue({
fieldId: "custitem_jj_magento_sync_status",
value: 'Updated'
});
objRecord.setValue({
fieldId: "custitem_jj_magento_sync_lastupdated",
value: new Date()
});
} else {// set on failure
objRecord.setValue({
fieldId: "custitem_jj_magento_sync_error",
value: (JSON.parse(productUpdateResponse.body).message == 'The Product with the "%1" SKU doesn' + "'t" + ' exist.') ? "The item failed to sync with Magento because one or more related items are not synced with Magento. Please sync the related items first" : JSON.parse(productUpdateResponse.body).message
});
objRecord.setValue({
fieldId: "custitem_jj_magento_sync_status",
value: 'Failed'
});
}
//update the special price
(promotionPriceOld && promotionPriceOld > 0) ? specialPrizeSync(isPromotion, promotionPrice, sku, promotionPriceOld) : specialPrizeSync(isPromotion, promotionPrice, sku)
//update image object to attach extra images to the product
itemImageSync(scriptContext, objRecord, newRec, oldRec, sku, 'update'); //image*
objRecord.save({
ignoreMandatoryFields: true,
enableSourcing: false
});
/* STOCK UPDATE - Start*/
stockUpdate(scriptContext, 'edit')
/* STOCK UPDATE - End*/
return true;
}
} catch (e) {
log.error("Error @ Update Item", e.message);
}
}
/**
* Function to set the special price for a product in Magento
* @param {Object} newRec
* @param {String} sku
*/
function specialPrizeSync(isPromotion, promotionPrice, sku, promotionPriceOld) {
try {
let specialPrizeResponse;
if (isPromotion && (promotionPrice && promotionPrice > 0)) {
//Call the POST API to Create the special prize for an item.
let specialPrizeCreateApi = magentolib.ADOBE_API_REQUESTS.CREATE_SPECIAL_PRICE;
specialPrizeResponse = magentolib.commonFunctions.sendAPIRequest(
"POST",
specialPrizeCreateApi,
{
"prices": [
{
"price": parseFloat(promotionPrice),
"store_id": 0,
"sku": sku,
"price_from": "2023-07-31 00:00:00",
"price_to": "2023-08-31 00:00:00",
"extension_attributes": {}
}
]
}
);
} else {
if (promotionPriceOld && promotionPriceOld > 0) {
//Call the POST API to delete the special prize
let specialPrizeDeleteApi = magentolib.ADOBE_API_REQUESTS.DELETE_SPECIAL_PRICE;
specialPrizeResponse = magentolib.commonFunctions.sendAPIRequest(
"POST",
specialPrizeDeleteApi,
{
"prices": [
{
"price": promotionPriceOld,
"store_id": 0,
"sku": sku,
"price_from": "2023-07-31 00:00:00",
"price_to": "2023-07-31 00:00:00",
"extension_attributes": {}
}
]
}
);
}
}
} catch (e) {
log.error("Error @ specialPrizeSync", e.message)
}
}
/**
* Sync additional images separately to Adobe commerce
* @param {Object} scriptContext
* @param {Record} objRecord
* @param {Record} newRec
* @param {Record} oldRec
* @param {String} sku
* @param {Sting} type
*/
function itemImageSync(scriptContext, objRecord, newRec, oldRec, sku, type) {
try {
let imageObjRef = {};
for (let index = 0; index <= 5; index++) {
let imageFieldIdNew = '';
let imageFieldIdOld = '';
if (type === "update") {
if (index === 0) {
imageFieldIdNew = objRecord.getValue({ fieldId: 'custitem_atlas_item_image' });
imageFieldIdOld = oldRec.getValue({ fieldId: 'custitem_atlas_item_image' });
imageObjRef = {
"entry": {
"media_type": "image",
"label": "Image",
"disabled": false,
"types": [
"thumbnail",
"small_image",
"image"],
"content": {}
}
}
}
else {
imageFieldIdNew = objRecord.getValue({ fieldId: `custitem_jj_image${index}_` });
imageFieldIdOld = oldRec.getValue({ fieldId: `custitem_jj_image${index}_` });
imageObjRef = {
"entry": {
"media_type": "image",
"label": "Image",
"disabled": false,
"types": [],
"content": {}
}
}
}
}
if (type === 'create') {
imageFieldIdNew = objRecord.getValue({ fieldId: `custitem_jj_image${index}_` });
imageObjRef = {
"entry": {
"media_type": "image",
"label": "Image",
"disabled": false,
"types": [],
"content": {}
}
}
if (imageFieldIdNew) {
addImage(objRecord, imageFieldIdNew, imageObjRef, index, sku);
}
} else {
if (imageFieldIdOld !== imageFieldIdNew) {
if (imageFieldIdOld) {
//delete old image from Adobe commerce
let idOldImage = index == 0 ? oldRec.getValue({ fieldId: 'custitem_jj_image_id' }) : oldRec.getValue({ fieldId: `custitem_jj_image${index}_id` });
if (idOldImage) {
let imageDelApi = magentolib.ADOBE_API_REQUESTS.DELETE_IMAGE;
imageDelApi = imageDelApi.replace('{sku}', sku);
imageDelApi = imageDelApi.replace('{entryId}', idOldImage);
try {
let imageDelResponse = magentolib.commonFunctions.sendAPIRequest(
"DELETE",
imageDelApi,
);
if (imageDelResponse.code === 200) {
objRecord.setValue({
fieldId: index == 0 ? 'custitem_jj_image_id' : `custitem_jj_image${index}_id`,
value: null
});
}
} catch (e) {
log.error('error@ del image', e);
}
}
}
if (imageFieldIdNew) {
//create new image in Adobe commerce
addImage(objRecord, imageFieldIdNew, imageObjRef, index, sku);
}
}
}
//If no images, then disable the product in Magento.
let imgOne = newRec.getValue({ fieldId: 'custitem_jj_image1_' });
let imgTwo = newRec.getValue({ fieldId: 'custitem_jj_image2_' });
let imgThree = newRec.getValue({ fieldId: 'custitem_jj_image3_' });
let imgFour = newRec.getValue({ fieldId: 'custitem_jj_image4_' });
let imgFive = newRec.getValue({ fieldId: 'custitem_jj_image5_' });
let mainImg = objRecord.getValue({ fieldId: 'custitem_atlas_item_image' });
//Call the update API to disable the product in website if no images present for this item in netsuite
if (!imgOne && !imgTwo && !imgThree && !imgFour && !imgFive && !mainImg) {
let productUpdateApi = magentolib.ADOBE_API_REQUESTS.UPDATE_PRODUCT;
productUpdateApi = productUpdateApi.replace('{sku}', sku);
magentolib.commonFunctions.sendAPIRequest(
"PUT",
productUpdateApi,
{
"product": {
"status": 2
}
}
);
}
}
} catch (e) {
log.error('error@imageItemSync', e);
}
}
/**
* Add new product images to Adobe commerce
* @param {Record} objRecord
* @param {String/Number} imageFieldId
* @param {Object[Object]} imageObjRef
* @param {Number} index
* @param {String} type
* @param {String} sku
*/
function addImage(objRecord, imageFieldId, imageObjRef, index, sku) {
try {
if (imageFieldId) {
let urlPattern = /^(https?|ftp|file):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$/;
let imageContents;
let curImageType;
if (urlPattern.test(imageFieldId)) {
// If the value is a URL, convert it to base64
let imageData = convertUrlToBase64(imageFieldId);
imageContents = imageData.base64Data;
curImageType = imageData.imageType;
} else {
// If the value is an image file, load its contents and retrieve the file type
let fileObj = file.load({ id: imageFieldId });
imageContents = fileObj.getContents();
curImageType = jjutil.savedSearch.fetchImageType(fileObj.fileType);
}
let currentTime = Math.round(+new Date() / 1000);
let curImageName = `image_${currentTime}.${curImageType}`;
let imageType = "image/" + curImageType;
imageObjRef.entry.content.base64_encoded_data = imageContents;
imageObjRef.entry.content.type = imageType;
imageObjRef.entry.content.name = curImageName;
let imageDetailObject = JSON.stringify(imageObjRef);
let imageAddApi = magentolib.ADOBE_API_REQUESTS.CREATE_IMAGE;
imageAddApi = imageAddApi.replace('{sku}', sku);
let imageAddResponse = magentolib.commonFunctions.sendAPIRequest(
"POST",
imageAddApi,
JSON.parse(imageDetailObject)
);
if (imageAddResponse && imageAddResponse.headers && JSON.parse(imageAddResponse.body)) {
let resbdy = JSON.parse(imageAddResponse.body);
resbdy = resbdy.replace(/^"(.*)"$/, '$1');
//set the Adobe Image id to Netsuite
if (imageAddResponse.code == 200) {
objRecord.setValue({
fieldId: index == 0 ? 'custitem_jj_image_id' : `custitem_jj_image${index}_id`,
value: resbdy
});
}
}
}
} catch (e) {
log.error('error@create image', e.message);
}
}
/**
* Function to convert the image url to base64 format
* @param {*} imageUrl
* @returns {object}
*/
function convertUrlToBase64(imageUrl) {
try {
// Make a GET request to the image URL
let response = https.get({
url: imageUrl
});
// Get the response headers
let headers = response.headers;
// Extract the content type header, which contains the image type
let contentType = headers['content-type'];
// Extract the image type from the content type header
let imageType = contentType.split('/').pop().toLowerCase();
// Get the response body as base64 data
let base64Data = response.body.toString('base64');
return {
base64Data: base64Data,
imageType: imageType
};
} catch (e) {
log.error('error@ convertUrlToBase64', e.message);
}
}
/**
* set price levels in the API payload
* @param {record object} objRecord
* @returns {Array [Object]}
*/
function setPriceLevels(objRecord, includePromotionPrice, nonDiscountable) {
let tierPrices = [];
try {
let basePrice;
let lineCount = objRecord.getLineCount({ sublistId: 'price1' });//The linecount of pricelevels
for (let m = 0; m < lineCount; m++) {
let element = {};
let pricelevel = objRecord.getSublistValue({
sublistId: 'price1',
fieldId: 'pricelevel',
line: m
});
let price = objRecord.getSublistValue({
sublistId: 'price1',
fieldId: 'price_1_',
line: m
});
let discount = objRecord.getSublistValue({
sublistId: 'price1',
fieldId: 'discount',
line: m
});
if (price.length == 0) {
continue;
}
element.id = pricelevel;
element.value = price;
element.discount = discount;
if (pricelevel) {
if (pricelevel == 1) {
basePrice = price;
}
let magentoPricelevelObj = jjutil.savedSearch.priceLevelSearch(pricelevel);
let priceId = magentoPricelevelObj.adobePricelevelId;
if (priceId) {
let Obj =
{
"customer_group_id": priceId,
"qty": 1,
"value": parseFloat(element.value),
"extension_attributes": {
"website_id": 0
}
};
tierPrices.push(Obj);
}
}
}
//if the non discountable checkbox is checked, then the price in all levels should be the base price.
//if the "Included in promotion" checkbox is checked, then the price in all levels should be the promotion price.
if (nonDiscountable) {
let priceInAllLevels = basePrice;
for (let k in tierPrices) {
tierPrices[k].value = parseFloat(priceInAllLevels);
}
}
return tierPrices;
} catch (e) {
log.error("error@setPriceLevels", e);
return tierPrices;
}
}
/**
* set category in the API payload
* @param {Array [Object]} customAttributes
* @param {String} category
* @returns {Array [Object]}
*/
function setCategory(context, customAttributes, category, categoryOld, sku) {
try {
let categoryLinks = [];
//check if the old category has changed or not. If changed, then unset the current category list from magento product
if ((context == 'update') && ((category.length !== categoryOld.length) || (!category.every(element => categoryOld.includes(element))))) {
//Call the update API to unset the product category
let productUpdateApi = magentolib.ADOBE_API_REQUESTS.UPDATE_PRODUCT;
productUpdateApi = productUpdateApi.replace('{sku}', sku);
magentolib.commonFunctions.sendAPIRequest(
"PUT",
productUpdateApi,
{
"product": {
"extensionAttributes": {
"category_links": [
]
}
}
}
);
}
//new category
let categoryArr = [];
if (category.length > 0) {
categoryArr = jjutil.savedSearch.itemCategorySearch(category);
}
let newCategoryArr = [];
for (let i = 0; i < categoryArr.length; i++) {
if (categoryArr[i]?.magentoID) {
newCategoryArr.push(categoryArr[i]?.magentoID);
}
}
if (newCategoryArr && Array.isArray(newCategoryArr)) {
customAttributes.push({
"attribute_code": "category_ids",
"value": newCategoryArr
});
}
for (let i = 0; i < newCategoryArr.length; i++) {
let catObj = {
"position": 0,
"category_id": newCategoryArr[i]
};
categoryLinks.push(catObj);
}
return categoryLinks;
} catch (e) {
log.error('error@categorydelete', e);
return [];
}
}
/**
* get all the related product as an object
* @param {record object} objRecord
* @param {String} sku
* @returns {Array [Object]}
*/
function getlinkProduct(objRecord, sku) {
let linkProducts = [];
try {
let linkCount = objRecord.getLineCount({ sublistId: 'presentationitem' });
for (let l = 1; l <= linkCount; l++) {
let linkSku = objRecord.getSublistValue({
sublistId: 'presentationitem',
fieldId: 'presentationitem_display',
line: l - 1
});
let relatedItem = {
"sku": sku,
"link_type": "related",
"linked_product_sku": linkSku,
"linked_product_type": "simple",
"position": l
};
linkProducts.push(relatedItem);
}
return linkProducts;
} catch (e) {
log.error('e@getlinkProduct', e);
return linkProducts;
}
}
/**
* Set additional fields(custom fields) in the Adobe commerce
* @param {record object} newRec
* @param {Array (Object)} customAttributes
* @param {String} context
*/
function commonSetAttrFun(newRec, customAttributes) {
let metaUrl, productName;
try {
let itemStatus = newRec.getValue({ fieldId: 'custitem10' });
productName = newRec.getValue({ fieldId: 'storedisplayname' });
metaUrl = convertToHyphen(productName)
let isFeaturedProduct = newRec.getValue({ fieldId: 'custitem_jj_featuredproduct' });
let isNew = newRec.getValue({ fieldId: 'custitem_jj_newproduct' });
let isNonDiscountable = newRec.getValue({ fieldId: 'custitem22' });
let description = newRec.getValue({ fieldId: 'storedescription' });
customAttributes = [{
"attribute_code": 'item_status',
"value": STATUSMAPPING[itemStatus]
},
{
"attribute_code": 'featured_product_option',
"value": (isFeaturedProduct == true) ? "1" : "0"
},
{
"attribute_code": 'new_product',
"value": (isNew == true) ? "1" : "0"
},
{
"attribute_code": 'non_discountable',
"value": (isNonDiscountable == true) ? "1" : "0"
},
{
"attribute_code": 'short_description',
"value": description ? description : ""
},
{
"attribute_code": 'url_key',
"value": metaUrl
},
{
"attribute_code": 'meta_title',
"value": productName
}
];
return customAttributes;
} catch (e) {
log.error('error@commonSetAttrFun', e);
return [{
"attribute_code": 'item_status',
"value": ""
},
{
"attribute_code": 'featured_product_option',
"value": ""
},
{
"attribute_code": 'new_product',
"value": ""
},
{
"attribute_code": 'non_discountable',
"value": ""
},
{
"attribute_code": 'short_description',
"value": ""
},
{
"attribute_code": 'url_key',
"value": metaUrl
},
{
"attribute_code": 'meta_title',
"value": productName
}
];
}
}
/**
* Function to convert weight units oz, kg and g to lb
* @param {Number} weight
* @param {Number} unit
* @returns {Number}
*/
function convertToPounds(weight, unit) {
try {
unit = unit.toLowerCase();
if (unit == 1) {
return weight;
} else if (unit == 2) {
return weight * 0.0625;
} else if (unit == 3) {
return weight * 2.20462;
} else if (unit == 4) {
return weight * 0.00220462;
} else {
log.debug("Invalid unit. Please use one of the following: 'lb', 'kg', 'g', 'oz'");
}
} catch (e) {
log.error("Error @ convertToPounds", e.message);
return false;
}
}
/**
* Function to convert a string in to a url format
* @param {String} str
* @returns {String}
*/
function convertToHyphen(str) {
try {
// Convert the string to lowercase
str = str.toLowerCase();
// Replace special characters, spaces, and underscores with hyphens
const hyphenatedStr = str.replace(/[^a-z0-9]+/g, '-');
// Remove consecutive hyphens
return hyphenatedStr.replace(/-+/g, '-');
} catch (e) {
log.error("Error @ convertToHyphen", e.message)
}
}
/**
* Stock sync: It will sync the inventory stock to Adobe commerce
* @param {record Object} scriptContext
* @param {string} type
*/
function stockUpdate(scriptContext, type) {
try {
if (scriptContext.newRecord.type == 'inventoryitem') {
if (type === 'create' || type === 'edit') {
let sku = scriptContext.newRecord.getValue({ fieldId: 'itemid' });
/***remove the default location from the item*****/
let payLoad1 = []
payLoad1.push({
"sku": sku,
"source_code": 'default'
});
if (payLoad1 && payLoad1.length) {
payLoad1 = {
"sourceItems": payLoad1
}
let stockUpdateAPI = magentolib.ADOBE_API_REQUESTS.DELETE_SOURCE_ITEM_LINK;
let response1 = magentolib.commonFunctions.sendAPIRequest(
"POST",
stockUpdateAPI,
payLoad1
);
}
/********************Sync end*****************************/
/***Sync all locations and quantity from the item record *****/
let payLoad2 = getPayLoad(scriptContext.newRecord.id);
if (payLoad2 && payLoad2.length) {
payLoad2 = {
"sourceItems": payLoad2
}
let stockUpdateAPI = magentolib.ADOBE_API_REQUESTS.SOURCE_ITEM_LINK;
let response2 = magentolib.commonFunctions.sendAPIRequest(
"POST",
stockUpdateAPI,
payLoad2
);
}
/********************sync end*****************************/
}
}
} catch (e) {
log.error("error@stock update", e);
}
}
/**
* Prepare payload for stock update API call
* @param {String/Integer} id
* @returns {Array [Object]}
*/
function getPayLoad(id) {
try {
let payLoad = [];
if (id) {
let inventoryitemSearchObj = search.create({
type: "inventoryitem",
filters:
[
["custitem_jj_adobe_item_id", "isnotempty", ""],
"AND",
["type", "anyof", "InvtPart"],
"AND",
["internalid", "anyof", id],
"AND",
["inventorylocation.custrecord_jj_adobe_source_code","isnotempty",""]
],
columns:
[
search.createColumn({ name: "internalid", label: "Internal ID" }),//0
search.createColumn({
name: "custitem_jj_adobe_item_id",
label: "Adobe Item ID"
}),//1
search.createColumn({
name: "itemid",
sort: search.Sort.ASC,
label: "Name"
}),//2
search.createColumn({
name: "locationquantityavailable",
label: "Location Available"
}),//3
search.createColumn({
name: "inventorylocation",
label: "Inventory Location"
}),//4
search.createColumn({
name: "custrecord_jj_adobe_source_code",
join: "inventoryLocation",
label: "Adobe Source Code"
}),//5
]
});
inventoryitemSearchObj.run().each(function (result) {
// .run().each has a limit of 4,000 results
//get SKU
let item = result.getValue(inventoryitemSearchObj.columns[2]);
if (item.indexOf(':') >= 0) {
item = item.split(':');
item = item[item.length - 1]
}
//Adobe Item Id
let adobeItemId = result.getValue(inventoryitemSearchObj.columns[1]);
//Quantity
let qty = result.getValue(inventoryitemSearchObj.columns[3]);
if (qty == undefined || qty == null)
qty = 0;
//Location Source_code
let source_code = result.getValue(inventoryitemSearchObj.columns[5]);
//only if source codes present for location and Adobe item ID present for this item
if (source_code && adobeItemId) {
payLoad.push({
"sku": item,
"quantity": Number(qty),
"source_code": source_code,
"status": (Number(qty) == 0 || Number(qty) == NaN) ? 0 : 1
});
}
return true;
});
}
return payLoad;
} catch (e) {
log.error('error@getPayLoad', e)
return []
}
}
return { beforeLoad, beforeSubmit, afterSubmit }
});