javascriptnetsuitesuitescriptbfo

Printing PDF in NetSuite with selection of Multiple Transaction Records


Is there a way to have a selection of many transactions printed into a single PDF document? I only see two options which seem to have significant drawbacks:

1) Load individual records into each of their own nlobjTemplateRenderer objects, and then stitch them all together within tags before rendering to PDF. Has a limit of less than 50 transactions depending on other actions taken when used within a Suitelet.

2) Do a search based upon internals IDs of selected records and pass the search results into a nlobjTemplateRenderer object. This method, based upon existing documentation, does not lead me to believe that it will properly display records with line data as result columns completely within a single document.

It almost seems like my best option is #1, but to split the desired transaction up into groups of 5-10 records and repeatedly calling a Suitelet with the small groups in the hopes of meeting the 45-second timeout limit of nlapiRequestURL before stitching together all of the results and returning the final PDF document. I pretty much see a basic form of that as the following:

// initial called function that will return completed PDF document file
function buildPdfFromRecords() {
    var pdfBuilder = [];
    var selectedIDs = [];
    var chunks = chunkify(selectedIDs, 10);
    for (var c = 0; c < chunks.length; c++) {
        var param = { id_list : JSON.stringify(chunks[s]) };
        var result = nlapiRequestURL(url, param).getBody();
        pdfBuilder.push(result);
    }
    var finalXML = "<pdfset>" + pdfBuilder.join("") + "</pdfset>";
    var pdfDoc = nlapiXMLToPDF(finalXML);
}

// function in suitelet called by url to handle individual groups of record internal IDs
// to mitigate scripting governance limits
function handleRecordIdListRequest(request, reponse) {
    var idList = JSON.parse(request.getParameter("id_list"));
    var templateXML = nlapiLoadRecord("template.txt").getValue();
    var pdfBuilder = [];
    for (var i = 0; i < idList.length; i++) {
        var transRecord = nlapiLoadRecord("recordtype", idList[i]);
        var renderer = nlapiCreateTemplateRenderer();
        renderer.setTemplate(templateXML);
        renderer.addRecord("record", transRecord);
        pdfBuilder.push(renderer.renderToString());
    }
    response.write(pdfBuilder.join(""));
}

If this is really the best way, then so be it, but I'm hoping there's a more elegant solution out there that I'm just not seeing.


Solution

  • There are a number of pieces you can stitch together to get this done.

    1. In the post handler of your Suitelet use the N/task library to schedule a map/reduce task. The task.submit method returns a taskId that you can use to monitor the progress of your job. Once your UI has a taskId it can periodically check to see if the task has completed. When complete you can show the generated .pdf. You could also let the user know that the pdf might take a few minutes to generate and offer to email it to them when done. Here's a snippet that schedules a scheduled script with parameters:

      const mrTask = task.create({
        taskType:task.TaskType.SCHEDULED_SCRIPT,
        scriptId:'customscript_knsi_batch_products',
        deploymentId: deploymentId,
        params: {
          custscript_knsi_batch_operator:user.id,
          custscript_knsi_batch_sourcing: sourcingId
        }
      });
    
      try{
        const taskId = mrTask.submit();
        context.response.setHeader({name:'content-type', value:'application/json'});
        context.response.write(JSON.stringify({
          success:true,
          message:'queued as task: '+ taskId
        }));
      }catch(e){
        log.error({
          title:'triggering '+ sourcingId +' for '+ user.email,
          details:(e.message || e.toString()) + (e.getStackTrace ? (' \n \n' + e.getStackTrace().join(' \n')) : '')
        });
        context.response.setHeader({name:'content-type', value:'application/json'});
        context.response.write(JSON.stringify({
          success:false,
          message:'An error occured scheduling this script\n'+e.message
        }));

    1. Use a Map/Reduce script where your map method generates and returns each transaction's pdf file url. You'll only have a single key so that the results of all map stages coalesce into a single reduce.
    2. In the reduce step you can generate open and close pdf files as necessary and put their references into your mapped array of pdfs.
    3. Use the pdfset to bind all your individual pdfs into a single pdf:

    function renderSet(opts){
    	var tpl = ['<?xml version="1.0"?>','<pdfset>'];
    
    	opts.files.forEach(function(id, idx){
    		const partFile = file.load({id:id});
    		var pdf_fileURL = xml.escape({xmlText:partFile.url});
    		tpl.push("<pdf src='" + pdf_fileURL + "'/>");
    	});
    
    	tpl.push("</pdfset>");
    
    	log.debug({title:'bound template', details:xml.escape({xmlText:tpl.join('\n')})});
    
    	return render.xmlToPdf({
    		xmlString:  tpl.join('\n')
    	});
    }