Scheduled integration of Item Receipt (IR) Sync from multiple TrackTraceRX to NetSuite

Scheduled integration of Item Receipt (IR) Sync from multiple TrackTraceRX to NetSuite

define(['N/record', 'N/search', 'N/format', 'N/config', '../../Common Library/jj_tracktracerx_ns_utility.js', '../../Config Module/jj_cm_tracktracerx_api_config.js'],
    /**
     * @param{record} record
     * @param{search} search
     * @param{format} format
     * @param{config} config
     * @param{jjUtil} jjUtil
     * @param{TrackTrace} TrackTrace
     */
    (record, search, format, config, jjUtil, trackTraceAPI) => {
        const TRACKTRACELIB = trackTraceAPI.Library;
        // UUID Fields for subsidiaries
        const ITEM_FIELD_UUID_DRUG = 'custrecord_jj_itemuuid_trcktrcerx_drug_';
        const ITEM_FIELD_UUID_NAKORN = 'custrecord_jj_itemuuid_trcktrcerx_nakorn';
        const ITEM_FIELD_UUID_GALAXY = 'custrecord_jj_itemuuid_trcktrcerx_galaxy';
        const ITEM_FIELD_UUID_BRIGHT = 'custrecord_jj_itemuuid_trcktrcerx_bright';

        // Subsidiary to UUID field mapping
        const subsidiaryUUIDMap = {
            29: ITEM_FIELD_UUID_BRIGHT,  // Brightline
            17: ITEM_FIELD_UUID_NAKORN,  // Nakorn
            26: ITEM_FIELD_UUID_GALAXY,  // Galaxy
            12: ITEM_FIELD_UUID_DRUG     // Drugplace
        };

        /**
         * @description Function to search UOM Details custom record with product UUID
         * @param {string} productUUID UUID of the product from the shipment
         * @returns {object|null} Matching UOM details record if found, null otherwise
         */
        function findUOMDetails(productUUID, subsidiaryValue) {
            try {
                let itemUuidField = subsidiaryUUIDMap[subsidiaryValue];
                let uomSearch = search.create({
                    type: "customrecord_jj_item_uomdetails", // Replace with the correct internal ID of your UOM details custom record
                    filters: [
                        [itemUuidField, "is", productUUID] // Assuming 'custrecord_product_uuid' is the field for product UUID
                    ],
                    columns: [
                        search.createColumn({ name: "internalid", label: "Internal ID" }),
                        search.createColumn({ name: "custrecord_jj_item_qty_lowestunit", label: "Lowest Unit of Sale" }),
                        search.createColumn({ name: "custrecord_jj_item_saleable_unit", label: "Unit" })
                    ]
                });

                let result = uomSearch.run().getRange({ start: 0, end: 1 });
                if (result.length > 0) {
                    let uomDetails = {
                        internalId: result[0].getValue({ name: "internalid" }),
                        lowestUnitOfSale: result[0].getValue({ name: "custrecord_jj_lowest_unit_of_sale" }),
                        unit: result[0].getValue({ name: "custrecord_jj_unit" })// Fetch the lowest unit field value
                    };
                    log.debug('Found UOM details', uomDetails);
                    return uomDetails;
                } else {
                    log.debug('No matching UOM found for product UUID:', productUUID);
                    return null;
                }
            } catch (e) {
                log.error('error@findUOMDetails', e);
                return null;
            }
        }


        /**
         * @description Function to fetch shipments from TrackTraceRX
         * @returns {Array} Array of shipment objects
         */
        function fetchShipmentsFromTrackTrace() {
            try {
                let shipmentResponse;
                let shipArray = [];
                let shipmentMainArray = [];
                let subsidiaries = ["29", "17", "12", "26",];
                for (let i = 0; i < 4; i++) {
                    let configSearch = TRACKTRACELIB.apiConfigfileSearch(subsidiaries[i]);
                    log.debug("configSearch search...........", configSearch)
                    let api = TRACKTRACELIB.TRACKTRACERX_API_REQUESTS.recevingshipmnets;
                    shipmentResponse = TRACKTRACELIB.requestToTrackTrace("GET", configSearch?.domain + '/' + configSearch?.version + api, "", configSearch);        
                    shipArray.push(shipmentResponse);                   
                }
                for (let j = 0; j < 4; j++) {
                    if (shipArray[j]?.code == 200) {
                        for (let k = 0; k < 4; k++) {
                            log.debug('Fetched Shipments', shipArray[k].body);
                            shipmentMainArray.push(shipArray[k].body.data)
                        }
                        return shipmentMainArray;
                    } else {
                        log.error('Error fetching shipments from TrackTraceRX', shipArray[j].message);
                        return [];
                    }


                }
            } catch (e) {
                log.error('error@fetchShipmentsFromTrackTrace', e);
                return [];
            }
        }


        function findSubsidiary(poNumber) {
            let purchaseorderSearchObj = search.create({
                type: "purchaseorder",
                filters:
                    [
                        ["type", "anyof", "PurchOrd"],
                        "AND",
                        ["numbertext", "is", poNumber]
                    ],
                columns:
                    [
                        search.createColumn({ name: "subsidiary", label: "Subsidiary" })
                    ]
            });
            let searchResultCount = purchaseorderSearchObj.runPaged().count;           
            let poSubsidiary;
            purchaseorderSearchObj.run().each(function (result) {
                poSubsidiary = result.getValue({
                    name: "subsidiary"
                })
            });
            return poSubsidiary;


        }
        /**
         * @description Function to search for open POs in NetSuite matching the shipment data
         * @param {string} poNumber PO Number from TrackTraceRX
         * @param {string} vendorUUID Vendor UUID from TrackTraceRX
         * @returns {object} Matching PO details if found
         */
        function findMatchingPO(poNumber, vendorUUID, subsidiaryPo) {        
            let vendorField = vendorSubsidiaryMap[subsidiaryPo];
            try {
                let poSearch = search.create({
                    type: "purchaseorder",
                    filters: [
                        ["numbertext", "is", poNumber],
                        "AND",
                        ["status", "anyof", "PurchOrd:D", "PurchOrd:E", "PurchOrd:B"],                       
                    ],
                    columns: [
                        search.createColumn({ name: "internalid", label: "internalid" }),
                        search.createColumn({ name: "location", label: "location" }),
                        search.createColumn({ name: "subsidiary", label: "subsidiary" })
                    ]
                });

                let result = poSearch.run().getRange({ start: 0, end: 10 });
                if (result.length > 0) {
                    log.debug('Found matching PO', result[0]);
                    return result[0];
                } else {
                    return null;
                }
            } catch (e) {
                log.error('error@findMatchingPO', e);
                return null;
            }
        }


        /**
         * @description Function to get detailed shipment information for a given shipment UUID
         * @param {string} shipmentUUID UUID of the shipment to retrieve details
         * @returns {object} Shipment details from TrackTraceRX
         */
        function getShipmentDetails(shipmentUUID, subsidiaryValue) {
            try {          
                for (let i = 0; i < 4; i++) {
                    let configSearch = TRACKTRACELIB.apiConfigfileSearch(subsidiaryValue);
                    let api = TRACKTRACELIB.TRACKTRACERX_API_REQUESTS.getSpecificShipment.replace('{shipment_type}', 'Inbound').replace('{shipment_uuid}', shipmentUUID);
                    let apiWithParams = api + "?is_convert_lowest_sealable_unit=true";
                    let shipmentDetailsResponse = TRACKTRACELIB.requestToTrackTrace("GET", configSearch?.domain + '/' + configSearch?.version + apiWithParams, "", configSearch);
             
                    if (shipmentDetailsResponse?.code == 200) {
                        log.debug('Fetched Shipment Details', shipmentDetailsResponse.body);
                        return shipmentDetailsResponse.body;
                    } else {
                        log.error('Error fetching shipment details', shipmentDetailsResponse.message);
                        return null;
                    }
                }
            } catch (e) {
                log.error('error@getShipmentDetails', e);
                return null;
            }
        }

        /**
         * @description Function to create or update the shipment integration record
         * @param {object} shipRespBody Shipment response body from TrackTraceRX
         * @param {string} nsShipId Netsuite Shipment Internal ID
         * @param {string} nsIntegrInfoId Transaction Integration Info Record Internal ID
         * @param {string} shipIntegrInfoId Shipment Integration Info Record Internal ID
         * @param {string} errorMsg Error occurred during creation
         */
        function createOrUpdateShipIntegrationRecord(shipRespBody, nsShipId, shipIntegrInfoId, errorMsg) {
            try {
                let recObj = {};
                if (jjUtil.checkForParameter(nsShipId)) {
                    recObj.custrecord_jj_shipment = nsShipId;                
                    recObj.custrecord_jj_shipment_uuid = shipRespBody?.uuid;
                    recObj.custrecord_jj_shipment_sync_status = "Created";
                    recObj.custrecord_jj_shipment_sync_error = "";
                    recObj.custrecord_jj_tracktrace_shipmen_payload = JSON.stringify(shipRespBody);
                } else {
                    recObj.custrecord_jj_tracktrace_transaction = "";                    
                    recObj.custrecord_jj_shipment_uuid = shipRespBody?.uuid || "";
                    recObj.custrecord_jj_shipment_sync_status = "Failed";
                    recObj.custrecord_jj_shipment_sync_error = errorMsg;
                    recObj.custrecord_jj_tracktrace_shipmen_payload = JSON.stringify(shipRespBody);
                }
                let integrationRecId;
                if (jjUtil.checkForParameter(shipIntegrInfoId)) {
                    integrationRecId = record.submitFields({
                        type: 'customrecord_jj_tracktrace_shipments',
                        id: shipIntegrInfoId,
                        values: recObj,
                        options: {
                            enableSourcing: false,
                            ignoreMandatoryFields: true
                        }
                    });
                } else {
                    let integrationRecObj = record.create({
                        type: 'customrecord_jj_tracktrace_shipments'
                    });
                    for (let key in recObj) {
                        integrationRecObj.setValue({ fieldId: key, value: recObj[key] });
                    }
                    integrationRecId = integrationRecObj.save({
                        enableSourcing: false,
                        ignoreMandatoryFields: true
                    });
                }
                log.debug('Shipment Integration Record Updated', integrationRecId);
            } catch (e) {
                log.error('error@createOrUpdateShipIntegrationRecord', e);
            }
        }
        /**
        * Function is defined to search the order integration ifo record and the shipment integration records created under this
        * @param {String} orderId SO/PO internal id
        * @returns {object}
        */
        function integrationInfoSearch(orderId) {
            try {
                let integrInfo = {};
                let customrecord_jj_tracktrace_integr_infoSearchObj = search.create({
                    type: "customrecord_jj_tracktrace_integr_info",
                    filters:
                        [
                            ["custrecord_jj_tracktrace_transaction", "anyof", orderId]
                        ],
                    columns:
                        [
                            search.createColumn({ name: "internalid", label: "Internal ID" }),
                            search.createColumn({ name: "custrecord_jj_tracktrace_transaction", label: "Transaction" }),
                            search.createColumn({ name: "custrecord_jj_transaction_uuid", label: "Transaction UUID" }),
                            search.createColumn({
                                name: "internalid",
                                join: "CUSTRECORD_JJ_TRANSACTION_INTEGRATION",
                                label: "Internal ID"
                            }),
                            search.createColumn({
                                name: "custrecord_jj_shipment",
                                join: "CUSTRECORD_JJ_TRANSACTION_INTEGRATION",
                                label: "Shipment"
                            }),
                            search.createColumn({
                                name: "custrecord_jj_shipment_uuid",
                                join: "CUSTRECORD_JJ_TRANSACTION_INTEGRATION",
                                label: "Shipment UUID"
                            })
                        ]
                });
                customrecord_jj_tracktrace_integr_infoSearchObj.run().each(function (result) {
                    // .run().each has a limit of 4,000 results
                    integrInfo.id = result.getValue({ name: "internalid", label: "Internal ID" });
                    integrInfo.orderid = result.getValue({ name: "internalid", label: "Internal ID" });
                    integrInfo.orderuuid = result.getValue({ name: "internalid", label: "Internal ID" });
                    let shipmentInfo = result.getValue({
                        name: "internalid",
                        join: "CUSTRECORD_JJ_TRANSACTION_INTEGRATION",
                        label: "Internal ID"
                    });
                    if (!integrInfo.shipments && shipmentInfo)
                        integrInfo.shipments = [];
                    if (shipmentInfo) {
                        let shipment = {}
                        shipment.id = shipmentInfo
                        shipment.shipmentid = result.getValue({
                            name: "custrecord_jj_shipment",
                            join: "CUSTRECORD_JJ_TRANSACTION_INTEGRATION",
                            label: "Shipment"
                        })
                        shipment.shipmentuuid = result.getValue({
                            name: "custrecord_jj_shipment_uuid",
                            join: "CUSTRECORD_JJ_TRANSACTION_INTEGRATION",
                            label: "Shipment UUID"
                        });
                        integrInfo.shipments.push(shipment);
                    }


                    return true;
                });
                return integrInfo;
            } catch (e) {
                log.debug('error@integrationInfoSearch', e);
                return false;
            }
        }
        /**
* Function defined to create the shipment in Netsuite
* @param {string} orderId Order internal id
* @param {object} shipRespBody response body got from the shipment get API call
* @returns {object}
*/
        function createShipmentInNs(orderId, shipRespBody, subsidiaryValue) {
            try {
                // Get the shipment item lines
                let shipmentItems = shipRespBody?.ShipmentLineItem;
                log.debug("shipmentItems", shipmentItems)

                // Transform the order to Item Receipt
                let objRecord = record.transform({
                    fromType: 'purchaseorder',
                    fromId: orderId,
                    toType: 'itemreceipt',
                    isDynamic: true, // Ensure the record is dynamic
                });

                // Set the received date from the shipment response
                let receivedDate = shipRespBody?.received_on_event?.date;
                if (receivedDate) {
                    receivedDate = receivedDate.replace(" ", "T");
                    receivedDate = new Date(receivedDate + "Z");
                    objRecord.setValue({
                        fieldId: 'trandate', value: receivedDate
                    });
                }

                // Set memo field if notes exist in the shipment response
                objRecord.setValue({ fieldId: 'memo', value: shipRespBody?.notes || "" });
                let itemLineCount = objRecord.getLineCount({ sublistId: 'item' });

                for (let j = 0; j < itemLineCount; j++) {//Iterate through the IR item line
                    objRecord.selectLine({ sublistId: 'item', line: j });
                    let orderLine = objRecord.getCurrentSublistValue({
                        sublistId: 'item',
                        fieldId: 'orderline'
                    });
                    
                    let shipItemLine = shipmentItems
                    log.debug("shipItemLine", shipItemLine)
                    //Proceed if the IR item line exists in the TrackTraceRx shipment
                    if (shipItemLine) {
                        let shipmentItemObj = shipItemLine[0];
                        log.debug("shipmentItemObj", shipmentItemObj)
                        let uomDetails = findUOMDetails(shipmentItemObj.product.uuid, subsidiaryValue);
                        log.debug("uomDetails", uomDetails)
                        if (!uomDetails) {
                            log.debug('Skipping item with product UUID as no matching UOM was found', shipmentItemObj.product.uuid);
                            continue; // Skip this item if no UOM details found
                        }
                        let receivedQty = shipmentItemObj?.quantity;
                        log.debug("receivedQty", receivedQty)
                        if (uomDetails.unit === '2') {
                            // Perform special calculation for unit '2'
                            let lowestUOMQty = uomDetails.lowestUnitOfSale; // Assume the custom field for the lowest UOM quantity
                            if (lowestUOMQty && lowestUOMQty > 0) {
                                receivedQty = receivedQty / lowestUOMQty; // Convert quantity based on the lowest unit
                            }  // Example: multiply by 2 (you can replace this with any calculation logic needed)
                        }
                        //set item line
                        objRecord.setCurrentSublistValue({
                            sublistId: 'item',
                            fieldId: 'itemreceive',
                            value: true
                        });
                        objRecord.setCurrentSublistValue({
                            sublistId: 'item',
                            fieldId: 'quantity',
                            value: receivedQty
                        });
                        let objSubrecord = objRecord.getCurrentSublistSubrecord({
                            sublistId: 'item',
                            fieldId: 'inventorydetail'
                        });
                        let itemLots = shipmentItemObj?.product.lots;
                        log.debug("itemLots", itemLots)
                        //set inventory details subrecord
                        for (let l = 0; l < itemLots.length; l++) {
                            objSubrecord.selectLine({ sublistId: 'inventoryassignment', line: l });
                            objSubrecord.setCurrentSublistText({
                                sublistId: 'inventoryassignment',
                                fieldId: 'receiptinventorynumber',
                                text: itemLots[l]?.number ?? itemLots[l]?.lot_number
                            });
                            objSubrecord.setCurrentSublistValue({
                                sublistId: 'inventoryassignment',
                                fieldId: 'binnumber',//bin
                                value: 936//Receiving//itemLots[l]?.ti_notes
                            });
                            let expDate = itemLots[l]?.expiration_date;
                            let expDateObj = new Date(expDate);
                            let expDateStr = format.format({
                                value: expDateObj,
                                type: format.Type.DATETIMETZ,
                                timezone: 'GMT',
                            });
                            let expDateFormatedObj = format.parse({
                                value: expDateStr,
                                type: format.Type.DATE,
                                timezone: 'GMT'
                            });
                            objSubrecord.setCurrentSublistValue({
                                sublistId: 'inventoryassignment',
                                fieldId: 'expirationdate',
                                value: expDateFormatedObj
                            });
                            objSubrecord.setCurrentSublistValue({
                                sublistId: 'inventoryassignment',
                                fieldId: 'quantity',
                                value: itemLots[l]?.quantity
                            });
                            objSubrecord.commitLine({ sublistId: 'inventoryassignment' });
                        }
                        objRecord.commitLine({ sublistId: 'item' });
                    } else {
                        //remove the item line from IR
                        objRecord.selectLine({ sublistId: 'item', line: j });
                        objRecord.setCurrentSublistValue({
                            sublistId: 'item',
                            fieldId: 'itemreceive',
                            value: false
                        });
                        objRecord.commitLine({ sublistId: 'item' });
                    }
                }
                let shipId = objRecord.save({
                    enableSourcing: true,
                    ignoreMandatoryFields: true
                });
                return { id: shipId, error: '' };
            } catch (e) {
                log.error('error@createShipmentInNs', e);
                let error = jjUtil.checkForParameter(e?.message) ? e.message : e;
                return { id: '', error: error };
            }
        }


        /**
         * Defines the function that is executed at the beginning of the map/reduce process and generates the input data.
         * @returns {Array} The input data to use in the map/reduce process
         */
        const getInputData = () => {
            try {
                return fetchShipmentsFromTrackTrace();
            } catch (e) {
                log.error('error@getInputData', e);
                return [];
            }
        };

        /**
         * Defines the function that is executed when the reduce entry point is triggered.
         * @param {Object} reduceContext
         */
        const reduce = (reduceContext) => {
            try {
                let shipmentObj = JSON.parse(reduceContext.values[0]);              
                for (let i = 0; i < shipmentObj.length; i++) {                    
                    let poNumber = shipmentObj[i].transactions[0].po_number                 
                    let pouuid = shipmentObj[i].transactions[0].uuid                   
                    let vendorUUID = shipmentObj[i].trading_partner_uuid;                   
                    let subsidiaryPo = findSubsidiary(poNumber)
                    let poRecord = findMatchingPO(poNumber, vendorUUID, subsidiaryPo);                    
                    if (poRecord) {
                        let orderId = poRecord.getValue({ name: 'internalid' });
                        let subsidiaryValue = poRecord.getValue({ name: "subsidiary" });                       
                        let shipmentUUID = shipmentObj[i].uuid;
                        log.debug("shipmentUUID",shipmentUUID)
                        let shipmentDetails = getShipmentDetails(shipmentUUID, subsidiaryValue);                       
                        if (shipmentDetails) {
                            // Create Item Receipt in NetSuite and handle mapping
                            let shipNs = createShipmentInNs(orderId, shipmentDetails, subsidiaryValue);
                            record.submitFields({
                                type: 'purchaseorder',
                                id: orderId,
                                values: {
                                    custbody_jj_tracktrace_uuid: pouuid
                                },
                                options: {
                                    enableSourcing: false,
                                    ignoreMandatoryFields: true
                                }
                            });

                            createOrUpdateShipIntegrationRecord(shipmentDetails, shipNs?.id, '', shipNs?.error);
                        }
                    } else {
                        log.debug('No matching PO found for PO Number:', poNumber);
                    }
                }
            } catch (e) {
                log.error('error@reduce', e);
            }
        };

        return { getInputData, reduce };
    });


Leave a comment

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