Saturday 8 February 2020

How to create graph based custom report ?

Thanks to NetSuite - SuiteAnswers for great help, I referenced the code used in SuiteAnswers.

I have created a custom downloadable report/graph in pdf format generated from saved search results.

As per NS suite answer's suggestion, I have used BFO and SuiteLet in SuiteScript 2.0 version to design this. 

Design approach:

I have used "Search" module and "Render" modules in SuiteLet also BFO. 

Search - to create search and results.

Render - this module encapsulates functionality for printing, PDF creation, form creation from templates, and email creation from templates.

BFO can generate graphs like pie graphs, line graphs, bar graphs etc. For more info about BFO, one can download their guide from here: http://faceless.org/products/report/docs/userguide.pdf
BFO is also supported in SuiteScript and one can read about it on the help guide. The sample code below does the following:
  1. Search all cancelled Sales Orders and group them by location
  2. Generate an XML markup that conforms with BFO standards
  3. Transforms the XML markup to PDF using render module
  4. Prints the pie graph onto the screen
Sample Code:

/**
 * @NApiVersion 2.x
 * @NScriptType Suitelet
 * @NModuleScope SameAccount
 */
define(['N/search', 'N/render'],
    function(search, render) {
        function onRequest(context) {
            var salesorderSearchObj = search.create({
                type: "salesorder",
                filters: [
                    ["type", "anyof", "SalesOrd"],
                    "AND",
                    ["mainline", "is", "T"],
                    "AND",
                    ["datecreated","within","01/01/2019 12-00 am","02/03/2020 11-59 pm"]
                ],
                columns: [
                    search.createColumn({
                        name: "formulatext",
                        summary: "GROUP",
                        formula: "TO_CHAR({datecreated}, 'Month')",
                        label: "Formula (Text)"
                    }),
                    search.createColumn({
                        name: "formulanumeric",
                        summary: "GROUP",
                        formula: "TO_CHAR({datecreated}, 'mm')",
                        sort: search.Sort.ASC,
                        label: "Formula (Numeric)"
                    }),
                    search.createColumn({
                        name: "location",
                        summary: "GROUP",
                        label: "Location"
                    }),
                    search.createColumn({
                        name: "internalid",
                        summary: "COUNT",
                        label: "Internal ID"
                    })
                ]
            });
            var xml = '';
            xml += '<?xml version="1.0"?>\n<!DOCTYPE pdf PUBLIC "-//big.faceless.org//report" "report-1.1.dtd">\n';
            xml += '<pdf>\n';
            xml += '<head>\n' ;
            xml += '<meta name="title" value="Number of Sales Orders per Location"/>\n';
            xml += '</head>\n';
            xml += '<body>\n';
            xml += '<piegraph width="200" height="150" yrotation="30" display-key="flat-outer">\n';

            salesorderSearchObj.run().each(function(result) {
                var month = result.getValue({
                    name: "formulatext",
                    summary: "GROUP"
                });
                var location = result.getText({
                    name: "location",
                    summary: "GROUP"
                });
                var numOrders = result.getValue({
                    name: "internalid",
                    summary: "COUNT"
                });
                xml += '<gdata name="' + month + '" name2="' + location + '" value="' + numOrders + '"/>'; //getText is used for displaying the names of the locations.
                return true;
            });
            xml += '</piegraph>\n';
            xml += '</body>\n</pdf>';

            var renderer = render.create();
            renderer.templateContent = xml;
            var newfile = renderer.renderAsPdf();
            context.response.writeFile(newfile);
        }
        return {
            onRequest: onRequest
        };
    });

Output: