NetSuite – Magento Product Sync Using Rest API

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 }
        });

Leave a comment

Your email address will not be published. Required fields are marked *