Userevent script to get the transit time details from FedEx and UPS. The requested transit time details can be displayed in a tabular format in Item Fulfillment record.
const beforeLoad = (scriptContext) => {
try {
if (scriptContext.type === scriptContext.UserEventType.CREATE) {
let newRecord = scriptContext.newRecord;
let shippingCarrier = newRecord.getValue({ fieldId: ‘shipcarrier’ });
let transactionDateObject = newRecord.getValue({ fieldId: ‘trandate’ });
let IFDateString = transactionDateObject.toISOString();String)
let transactionDate = IFDateString.split(‘T’)[0];
let relatedSalesOrder = newRecord.getValue({ fieldId: ‘createdfrom’ });
let detailsSO = salesOrderSearch(relatedSalesOrder);
let shipToCountry = detailsSO.shipCountry;
let shipToZip = detailsSO.shipZip;
let location = detailsSO.location;
let shipToCity = detailsSO.shipCity;
let shipToState = detailsSO.shipState;
let totalAmount = detailsSO.total;
let packageWeight = getPackageWeight(newRecord)
let shipFromCountry, shipFromZip, locationDetails;
if (location) {
locationDetails = getLocationDetails(location);
shipFromCountry = locationDetails.country;
shipFromZip = locationDetails.zipCode;
}
let serviceDetails;
if (shippingCarrier === ‘nonups’) {
let configDetailsFedEx = getConfigDetails(1);
if (configDetailsFedEx.access_token) {
serviceDetails = getTransitTimeDetailsFedEx(configDetailsFedEx, shipFromCountry, shipFromZip, shipToZip, shipToCountry, transactionDate, packageWeight, totalAmount, shipToCity, shipToState);
}
} else if (shippingCarrier === ‘ups’) {
let configDetailsUPS = getConfigDetails(2);
if (configDetailsUPS.access_token) {
serviceDetails = getTransitTimeDetailsUPS(configDetailsUPS, shipFromCountry, shipFromZip, shipToZip, shipToCountry, transactionDate, shipToCity, shipToState, packageWeight);
}
}
if (serviceDetails) {
let htmlContent = buildTransitTimeHtml(serviceDetails)
newRecord.setValue({
fieldId: ‘custbody_jj_transit_details’,
value: htmlContent
});
}
}
} catch (e) {
log.error(‘Error in beforeLoad’, e);
}
}
/**
* @description Get the package weight for a Sales Order.
* @param {Object} newRecord – The Sales Order record object.
* @returns {number} – The total package weight.
*/
function getPackageWeight(newRecord) {
try {
let totalPackageWeight = 0;
// Get the number of item lines
let lineCount = newRecord.getLineCount({ sublistId: ‘item’ });
// Iterate over each item line
for (let i = 0; i < lineCount; i++) {
// Get the package weight (custcol_jj_ee_pckg_wght) and quantity for the line
let packageWeight = newRecord.getSublistValue({
sublistId: ‘item’,
fieldId: ‘custcol_jj_ee_pckg_wght’,
line: i
});
let quantity = newRecord.getSublistValue({
sublistId: ‘item’,
fieldId: ‘quantity’,
line: i
});
let quantityRemaining = newRecord.getSublistValue({
sublistId: ‘item’,
fieldId: ‘quantityremaining’,
line: i
});
if (!packageWeight || isNaN(packageWeight)) {
packageWeight = 0;
}
// Check if quantity is null, undefined, or not a number
if (!quantity || isNaN(quantity)) {
quantity = 0;
}
if (quantity) {
totalPackageWeight += (packageWeight * quantity);
}
else {
totalPackageWeight += (packageWeight * quantityRemaining);
}
}
return totalPackageWeight;
} catch (e) {
log.error(‘Error calculating total package weight’, e);
return 0;
}
}
/**
* @description Get location details from location ID.
* @param {number} locationId – The internal ID of the location.
* @returns {Object|null} – The location details or null if an error occurs.
*/
function getLocationDetails(locationId) {
try {
let locationDetails = search.lookupFields({
type: search.Type.LOCATION,
id: locationId,
columns: [‘country’, ‘zip’]
});
let country = locationDetails.country;
let zipCode = locationDetails.zip;
return {
country: country,
zipCode: zipCode
};
} catch (e) {
log.error(‘Error retrieving location details’, e.toString());
return null;
}
}
/**
* @description Retrieves configuration details from the custom record `customrecord_jj_fedex_api_details` based on the provided record ID.
* @param {number} recordId – The internal ID of the custom record containing API configuration details.
* @returns {Object} configObj – An object containing the API configuration details, or an empty object if an error occurs.
*/
const getConfigDetails = (recordId) => {
try {
let configObj = {};
let customrecord_jj_fedex_api_detailsSearchObj = search.create({
type: “customrecord_jj_fedex_api_details”,
filters: [[“internalid”, “anyof”, recordId]],
columns: [
“custrecord_jj_client_id”,
“custrecord_jj_client_secret”,
“custrecord_jj_api_endpoint_url”,
“custrecord_jj_api_key”,
“custrecord_jj_account_number”,
“custrecord_jj_access_token”
]
});
let searchResultCount = customrecord_jj_fedex_api_detailsSearchObj.runPaged().count;
if (searchResultCount > 0) {
customrecord_jj_fedex_api_detailsSearchObj.run().each(function (result) {
configObj.client_id = result.getValue(“custrecord_jj_client_id”);
configObj.client_secret = result.getValue(“custrecord_jj_client_secret”);
configObj.posturl = result.getValue(“custrecord_jj_api_endpoint_url”);
configObj.api_key = result.getValue(“custrecord_jj_api_key”);
configObj.account_no = result.getValue(“custrecord_jj_account_number”);
configObj.access_token = result.getValue(“custrecord_jj_access_token”);
return true;
});
}
return configObj;
} catch (e) {
log.error(“Error@getConfigDetails”, e);
return {};
}
};
/**
* @description Fetch transit time details from FedEx API using the access token
* @param {Object} configDetailsFedEx – Configuration details including access token
* @param {string} zipCode – The shipping zip code
* @param {string} shipCountry – The shipping country
* @returns {Object} Transit time details
*/
const getTransitTimeDetailsFedEx = (configDetailsFedEx, shipFromCountry, shipFromZip, shipToZip, shipToCountry, transactionDate, packageWeight, totalAmount, shipToCity, shipToState) => {
try {
const headers = {
‘Accept’: ‘application/json’,
‘Content-Type’: ‘application/json’,
‘Authorization’: ‘Bearer ‘ + configDetailsFedEx.access_token,
};
const body = {
“accountNumber”: {
“value”: configDetailsFedEx.account_no
},
“rateRequestControlParameters”: {
“returnTransitTimes”: true,
“servicesNeededOnRateFailure”: true
},
“requestedShipment”: {
“shipper”: {
“address”: {
“postalCode”: shipFromZip,
“countryCode”: shipFromCountry
}
},
“rateRequestType”: [
“ACCOUNT”,
“LIST”
],
“recipient”: {
“address”: {
“postalCode”: shipToZip,
“countryCode”: shipToCountry
}
},
“shipDateStamp”: transactionDate,
“pickupType”: “DROPOFF_AT_FEDEX_LOCATION”,
“requestedPackageLineItems”: [
{
“weight”: {
“units”: “LB”,
“value”: packageWeight
},
},
],
“customsClearanceDetail”: {
“commodities”: [
{
“customsValue”: {
“amount”: totalAmount,
“currency”: “USD”
}
}
],
},
}
};
let url = ‘https://apis.fedex.com/rate/v1/rates/quotes’;
const response = https.request({
method: https.Method.POST,
url: url,
headers: headers,
body: JSON.stringify(body)
});
log.debug({
title: ‘FedEx API Response’,
details: response.body
});
let transitTimeDetails = ”;
if (response.code === 200) {
let responseObj = JSON.parse(response.body);
transitTimeDetails = extractTransitTimeDetails(responseObj);
}
return transitTimeDetails;
} catch (e) {
log.error(“FedEx Error@getTransitTimeDetailsFedEx”, e);
return {};
}
};
/**
* @description Extracts and formats transit time details from the FedEx API response object.
* @param {Object} responseObj – The response object returned from the FedEx API containing rate reply details.
* @returns {Array} serviceDetails – An array of objects, each containing shipping method, delivery date, delivery day, and delivery time.
*/
function extractTransitTimeDetails(responseObj) {
try {
let rateReplyDetails = responseObj.output.rateReplyDetails;
let serviceDetails = [];
// Iterate over rateReplyDetails to extract relevant details
rateReplyDetails.forEach(function (rateReplyDetail) {
let deliveryDateTime = rateReplyDetail.operationalDetail.deliveryDate;
let timePart = deliveryDateTime.split(‘T’)[1]; // Extract time part (e.g., “09:00:00”)
let datePart = deliveryDateTime.split(‘T’)[0]; // Extract date part
let details = {
shippingMethod: rateReplyDetail.serviceName,
deliveryDate: datePart,
deliveryDay: rateReplyDetail.operationalDetail.deliveryDay,
deliveryTime: timePart
};
serviceDetails.push(details);
});
return serviceDetails;
} catch (e) {
log.error(“Error @ extractTransitTimeDetails”, e.toString());
}
}
/**
* @description Fetches transit time details from the UPS API based on shipment information.
* @param {Object} configDetailsUPS – Configuration details for UPS API access, including access token.
* @param {string} shipFromCountry – Country code of the shipment origin.
* @param {string} shipFromZip – Postal code of the shipment origin.
* @param {string} shipToZip – Postal code of the shipment destination.
* @param {string} shipToCountry – Country code of the shipment destination.
* @param {string} transactionDate – Date of the shipment in YYYY-MM-DD format.
* @param {string} shipToCity – City of the shipment destination.
* @param {string} shipToState – State or province of the shipment destination.
* @param {number} packageWeight – Weight of the package in pounds.
* @returns {Array|string} – An array of service details if the request is successful, otherwise an error message.
*/
const getTransitTimeDetailsUPS = (configDetailsUPS, shipFromCountry, shipFromZip, shipToZip, shipToCountry, transactionDate, shipToCity, shipToState, packageWeight) => {
try {
let headers = {
‘transId’: ” “,
‘transactionSrc’: ‘testing’,
‘Content-Type’: ‘application/json’,
‘Accept’: ‘application/json’,
‘Authorization’: ‘Bearer ‘ + configDetailsUPS.access_token
};
let body = {
“originCountryCode”: shipFromCountry,
“originPostalCode”: shipFromZip,
“destinationCountryCode”: shipToCountry,
“destinationPostalCode”: shipToZip,
“weight”: packageWeight,
“weightUnitOfMeasure”: “LBS”,
“shipDate”: transactionDate
};
let url = ‘https://onlinetools.ups.com/api/shipments/v1/transittimes’;
let response = https.request({
method: https.Method.POST,
url: url,
headers: headers,
body: JSON.stringify(body)
});
let serviceDetails;
if (response.code === 200) {
let responseBody = JSON.parse(response.body);
serviceDetails = extractServiceDetails(responseBody);
} else {
return “Error fetching transit time”;
}
return serviceDetails;
} catch (e) {
log.error(“Error@getTransitTimeDetailsUPS”, e);
return “Error fetching transit time”;
}
};
/**
* @description Extracts service details from the UPS API response.
* @param {Object} responseBody – The response body from the UPS API.
* @returns {Array<Object>} serviceDetails – An array of service details.
*/
function extractServiceDetails(responseBody) {
try {
var emsResponse = responseBody.emsResponse;
var services = emsResponse.services;
var serviceDetails = [];
services.forEach(function (service) {
var details = {
shippingMethod: service.serviceLevelDescription,
deliveryDate: service.deliveryDate,
deliveryTime: service.deliveryTime,
deliveryDay: service.deliveryDayOfWeek
};
serviceDetails.push(details);
});
return serviceDetails;
} catch (e) {
log.error(“Error at extractServiceDetails”, extractServiceDetails);
}
}
/**
* @description Builds an HTML string to display transit time details in a styled format.
* @param {Array<Object>} serviceDetails – An array of service details to be displayed.
* @returns {String} htmlContent – The generated HTML content.
*/
function buildTransitTimeHtml(serviceDetails) {
try {
if (!serviceDetails || serviceDetails.length === 0) {
return ‘<p>No transit time details available.</p>’;
}
let htmlContent = `
<div class=”fedex-container”>
<style>
.fedex-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 20px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #f9f9f9;
}
.fedex-box {
flex: 1 1 30%;
box-sizing: border-box;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.fedex-box p {
margin-bottom: 0;
font-size: 14px;
color: #666;
}
</style>
`;
serviceDetails.forEach(detail => {
let shippingMethod = detail.shippingMethod || ‘N/A’;
let deliveryDate = detail.deliveryDate || ‘N/A’;
let deliveryTime = detail.deliveryTime || ‘N/A’;
let deliveryDay = detail.deliveryDay || ‘N/A’;
htmlContent += `
<div class=”fedex-box”>
<p><strong>Shipping Method:</strong> ${shippingMethod}</p>
<p><strong>Day:</strong> ${deliveryDay}</p>
<p><strong>Date:</strong> ${deliveryDate}</p>
<p><strong>Time:</strong> ${deliveryTime}</p>
</div>
`;
});
htmlContent += ‘</div>’;
return htmlContent;
} catch (error) {
console.error(‘Error generating transit time HTML:’, error);
return ‘<p>An error occurred while retrieving transit time details.</p>’;
}
}
/**
* @description Performs a search for a specific sales order based on the given internal ID and returns relevant shipping and order details.
* @param {number|string} relatedSalesOrder – The internal ID of the sales order to search for.
* @returns {Object|null} salesOrderData – An object containing details of the sales order if found, otherwise null.
*/
function salesOrderSearch(relatedSalesOrder) {
try {
let salesOrderSearchObj = search.create({
type: “salesorder”,
filters: [
[“type”, “anyof”, “SalesOrd”], “AND”,
[“internalid”, “anyof”, relatedSalesOrder], “AND”,
[“taxline”, “is”, “F”], “AND”,
[“cogs”, “is”, “F”], “AND”,
[“shipping”, “is”, “F”]
],
columns: [
search.createColumn({ name: “statusref”, label: “Status” }),
search.createColumn({ name: “shipcountrycode”, label: “Shipping Country Code” }),
search.createColumn({ name: “shipzip”, label: “Shipping Zip” }),
search.createColumn({ name: “location”, label: “Location” }),
search.createColumn({ name: “total”, label: “Total” }),
search.createColumn({ name: “shipstate”, label: “Shipping State/Province” }),
search.createColumn({ name: “shipcity”, label: “Shipping City” }),
search.createColumn({ name: “shipaddress2”, label: “Shipping Address 2” }),
search.createColumn({ name: “shipaddress1”, label: “Shipping Address 1” }),
search.createColumn({ name: “shipaddressee”, label: “Shipping Addressee” }),
search.createColumn({ name: “shippingattention”, label: “Shipping Attention” })
]
});
let salesOrderData = {};
salesOrderSearchObj.run().each(function (result) {
salesOrderData.status = result.getText({ name: “statusref”, label: “Status” });
salesOrderData.shipCountry = result.getValue({ name: “shipcountrycode”, label: “Shipping Country code” });
salesOrderData.shipZip = result.getValue({ name: “shipzip”, label: “Shipping Zip” });
salesOrderData.location = result.getValue({ name: “location”, label: “Location” });
salesOrderData.total = result.getValue({ name: “total”, label: “Total” });
salesOrderData.shipState = result.getValue({ name: “shipstate”, label: “Shipping State/Province” });
salesOrderData.shipCity = result.getValue({ name: “shipcity”, label: “Shipping City” });
salesOrderData.shippingAdd2 = result.getValue({ name: “shipaddress2”, label: “Shipping Address 2” });
salesOrderData.shippingAdd1 = result.getValue({ name: “shipaddress1”, label: “Shipping Address 1” });
salesOrderData.shippingAddressee = result.getValue({ name: “shipaddressee”, label: “Shipping Addressee” });
salesOrderData.shippingAttention = result.getValue({ name: “shippingattention”, label: “Shipping Attention” });
return false;
});
return salesOrderData;
} catch (e) {
log.error(“error in salesOrderSearch”, e);
return null;
}
}
return { beforeLoad }
});