Knowledge Base Topic: Storing Files in NetSuite Using a RESTlet
Overview
This knowledge base article explains how to create and store files in NetSuite’s File Cabinet through a RESTlet. The RESTlet allows external systems to send files to NetSuite by making a POST request with the necessary details, such as record type, internal ID, file type, and file content. The RESTlet processes the request, validates the provided data, and then saves the file in the appropriate folder based on the record type.
Key Concepts
- RESTlet Script: A server-side script in NetSuite that allows external systems to interact with NetSuite data through standard HTTP methods like GET, POST, PUT, and DELETE.
- File Cabinet: NetSuite’s repository where files like images, PDFs, and documents are stored. Each file is stored in a folder and can be associated with different records, such as transactions or customer records.
- Folder Structure: The script defines specific folders in the File Cabinet where files are stored based on the record type. In this case,
FOLDER_ID_PENDING_DONATIONis used for pending donation records, andFOLDER_ID_OUTGOING_FUNDis used for outgoing fund records. - MIME Type and File Type Validation: The script ensures that the file content matches the specified MIME type or file type. Supported file types include PNG, JPG, and PDF.
- Base64 Encoding: Files sent to the RESTlet are expected to be in Base64 encoding. The script decodes this content and validates it before saving it in NetSuite.
Detailed Explanation
- Extracting Parameters: The script starts by extracting necessary parameters from the POST request, such as
record_type,internal_id,file_type, andfileContent. These parameters are validated to ensure they are provided and contain valid data. - MIME Type Handling: If the file content starts with a MIME type (e.g.,
data:image/png;base64,...), the script extracts this MIME type and removes it from the content string, leaving only the Base64-encoded file data. - Record Validation: Before storing the file, the script loads the NetSuite record corresponding to the
record_typeandinternal_idto ensure the record exists. If the record is not found, an error is returned. - Folder Selection: The script selects the correct folder in the File Cabinet based on the
record_type. For example, if the record type iscustomsale_jcs_pendingdonation, the file is stored in the folder specified byFOLDER_ID_PENDING_DONATION. - File Creation: The script creates a file in NetSuite using the validated data. It determines the correct NetSuite file type (e.g., PNG, JPG, PDF) and stores the file in the chosen folder with a name formatted as
<record type>_<internal id>.<file type>. - Error Handling: The script includes comprehensive error handling through try-catch blocks and logs any issues encountered during execution. Errors are collected in an
errorStackand returned in the response if the process fails. - Response Structure: Upon successful completion, the RESTlet returns a response with a status of
SUCCESS, along with the file ID of the newly created file. If there is an error, the response will include aFAILUREstatus with the reason for the failure.
Use Cases
- Integrating with External Systems: This RESTlet is particularly useful for integrating external systems (e.g., a customer portal) with NetSuite to store transaction-related files automatically.
- Automating Document Management: By automating file storage based on transaction types, businesses can streamline document management processes, ensuring all relevant files are stored in NetSuite with the correct associations.
Conclusion
This RESTlet provides a robust and flexible solution for storing files in NetSuite’s File Cabinet. By leveraging NetSuite’s file management capabilities, businesses can improve data integrity and streamline their document management workflows.
CODE:
/**
* @NApiVersion 2.1
* @NScriptType Restlet
*/
define(['N/file', 'N/record', 'N/search', 'N/encode', 'N/log'],
/**
* @param{file} file
* @param{record} record
* @param{search} search
* @param{encode} encode
* @param{log} log
*/
(file, record, search, encode, log) => {
// Global variables for folder IDs
const FOLDER_ID_PENDING_DONATION = 48317; // Replace with actual folder ID for Pending Donation
const FOLDER_ID_OUTGOING_FUND = 48318; // Replace with actual folder ID for Outgoing Fund
let errorStack = []; // Global variable for storing errors
/**
* To check whether a value exists in the parameter
* @param parameter
* @param parameterName
* @returns {boolean}
*/
function checkForParameter(parameter, parameterName) {
if (parameter !== "" && parameter !== null && parameter !== undefined && parameter !== false && parameter !== "null" && parameter !== "undefined" && parameter !== " " && parameter !== 'false') {
return true;
} else {
if (parameterName)
log.debug('Empty Value found', 'Empty Value for parameter ' + parameterName);
return false;
}
}
/**
* Common Try-Catch function
* @param dataObj
* @param functionName
*/
function applyTryCatch(dataObj, functionName) {
function tryCatch(myfunction, key) {
return function () {
try {
return myfunction.apply(this, arguments);
} catch (e) {
log.error("error in " + key, e);
errorStack.push(e.message);
if (errorStack.length > 0) {
log.debug('errorStack', errorStack);
}
return false;
}
};
}
for (let key in dataObj) {
if (typeof dataObj[key] === "function") {
dataObj[key] = tryCatch(dataObj[key], functionName + "." + key);
}
}
}
let apiMethods = {
/**
* Defines the function that is executed when a POST request is sent to a RESTlet.
* @param {string | Object} requestBody - The HTTP request body; request body is passed as a string when request
* Content-Type is 'text/plain' or parsed into an Object when request Content-Type is 'application/json' (in which case
* the body must be a valid JSON)
* @returns {string | Object} HTTP response body; returns a string when request Content-Type is 'text/plain'; returns an
* Object when request Content-Type is 'application/json' or 'application/xml'
*/
post: function (requestBody) {
try {
// Extract parameters from request
let recordType = requestBody.record_type;
let internalId = requestBody.internal_id;
let fileContent = requestBody.file_type;
// Check for missing required parameters
if (!checkForParameter(recordType, 'recordType') || !checkForParameter(internalId, 'internalId') || !checkForParameter(fileContent, 'fileContent')) {
throw new Error('Missing required parameters: recordType, internalId, or fileContent');
}
let fileType = requestBody.fileType;
let mimeType = requestBody.mimeType;
// Check if fileContent starts with a MIME type and extract it
if (fileContent.startsWith('data:')) {
let matches = fileContent.match(/^data:(.*?);base64,/);
if (matches) {
mimeType = matches[1]; // Extract the MIME type
fileContent = fileContent.replace(/^data:(.*?);base64,/, ''); // Remove MIME type from the content
} else {
throw new Error('Invalid base64 string');
}
}
// Validate that the internal ID corresponds to the correct record type
try {
record.load({
type: recordType,
id: internalId,
isDynamic: true,
});
} catch (e) {
throw new Error(`Record not found: ${recordType} with ID ${internalId}`);
}
// Determine folder based on record type
let folderId;
if (recordType === 'customsale_jcs_pendingdonation') {
folderId = FOLDER_ID_PENDING_DONATION;
} else if (recordType === 'customsale_jcs_incomingfund') {
folderId = FOLDER_ID_OUTGOING_FUND;
} else {
throw new Error(`Invalid record type: ${recordType}`);
}
// Determine the NetSuite file type based on fileType parameter or mimeType
let nsFileType;
if (mimeType) {
switch (mimeType.toLowerCase()) {
case 'image/png':
nsFileType = file.Type.PNGIMAGE;
fileType = '.png';
break;
case 'image/jpeg':
nsFileType = file.Type.JPGIMAGE;
fileType = '.jpg';
break;
case 'application/pdf':
nsFileType = file.Type.PDF;
fileType = '.pdf';
break;
default:
throw new Error(`Unsupported MIME type: ${mimeType}`);
}
} else if (fileType) {
switch (fileType.toLowerCase()) {
case '.png':
nsFileType = file.Type.PNGIMAGE;
break;
case '.jpg':
case '.jpeg': // Handle both .jpg and .jpeg extensions
nsFileType = file.Type.JPGIMAGE;
fileType = '.jpg';
break;
case '.pdf':
nsFileType = file.Type.PDF;
break;
default:
throw new Error(`Unsupported file type: ${fileType}`);
}
} else {
throw new Error('Either fileType or mimeType must be provided');
}
// Create file in NetSuite
let fileName = `${recordType}_${internalId}${fileType}`;
// Create and save the file in NetSuite
let fileObj = file.create({
name: fileName,
fileType: nsFileType,
contents: fileContent,
encoding: file.Encoding.BASE_64,
folder: folderId
});
let fileId = fileObj.save();;
//let fileId = newFile.save();
return { status: 'SUCCESS', reason: 'FILE_ADDED', data: { fileId: fileId } };
} catch (e) {
log.error('Error in POST function', e);
errorStack.push(e.message);
if (errorStack.length > 0) {
log.debug('errorStack', errorStack);
}
return { status: 'FAILURE', reason: e.message, data: null };
}
}
};
applyTryCatch(apiMethods, 'apiMethods');
let main = {
/**
* Get Entry point of RESTlet
* @param context. Default parameter of RESTlet
* @returns {boolean}
*/
post: function (requestBody) {
return apiMethods.post(requestBody);
},
/**
* All response will be sent from this common point
* @param STATUS
* @param REASON
* @param DATA
* @returns {boolean}
*/
sendResponse: function (STATUS, REASON, DATA) {
return JSON.stringify({
summary: { status: STATUS, reason: REASON || null, error: errorStack || null },
data: DATA || null
});
}
};
applyTryCatch(main, 'main');
return main;
});