This script is used to auto populate the transit time details in item fulfillment record. The details are fetched from FedEx and UPS API. While clicking the Fulfill button the transit time details are populated below the Shipping Subtab.
(https, log, record, search) => {
/**
* 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 {
if (scriptContext.type === scriptContext.UserEventType.CREATE) {
let newRecord = scriptContext.newRecord;
let subsidiary = newRecord.getValue({ fieldId: ‘subsidiary’ });
log.debug(‘Subsidiary’, subsidiary);
// Check if the subsidiary is either 3 or 7
if (subsidiary == 3 || subsidiary == 7) {
let shippingCarrier = newRecord.getValue({ fieldId: ‘shipcarrier’ });
let transactionDateObject = newRecord.getValue({ fieldId: ‘trandate’ });
let IFDateString = transactionDateObject.toISOString();
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 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);
}
} else if (shippingCarrier === ‘ups’) {
let configDetailsUPS = getConfigDetails(2);
if (configDetailsUPS.access_token) {
serviceDetails = getTransitTimeDetailsUPS(configDetailsUPS, shipFromCountry, shipFromZip, shipToZip, shipToCountry, transactionDate, packageWeight);
}
}
if (serviceDetails) {
let htmlContent = buildTransitTimeHtml(serviceDetails)
newRecord.setValue({
fieldId: ‘custbody_jj_transit_details’,
value: htmlContent
});
}
else {
newRecord.setValue({
fieldId: ‘custbody_jj_transit_details’,
value: ‘No transit time details available.’
});
}
} else {
log.debug(‘Subsidiary not 3 or 7’, `Skipping processing for subsidiary ${subsidiary}`);
}
}
} 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
});
log.debug(“weight”, packageWeight)
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;
});
}
log.debug(“ConfigObj”, configObj)
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) => {
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
},
},
],
}
};
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);
});
log.debug(‘Service Details’, serviceDetails);
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, 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: ${error.message || error}</p>`;
//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 }
});