Tuesday 15 October 2019

How to upload CSV file to SFTP server and download any file to File Cabinet?

Hello everyone,

Below are the steps which i followed to make connection with SFTP server and download the file from SFTP server.

Considered 2 scripts here, 1. Map/Reduce, 2. SuiteLet

1. Map/Reduce:

  • In getInputData() - created a saved search - Created the Customer payments search in my case.
  • processed the data from "map" function(key/value pair)
  • In Summerize section collected the data and pushed the results into csv file and created the file in a folder in file cabinet.
  • Established a connection with SFTP server (Received the Password GUID from the suitelet)
  • Uploaded the CSV file in SFTP server (unfortunately the considered test sftp server doesn't have the "write" permission hence i'm unable to upload, but this code works you can trust and use this, as it worked for one real time sftp server).
  • Downloaded a ".txt" file SFTP server in file cabinet. 
2. Suitelet:
  • Created 2 fields in form one for user name capturing and another credential field is password field (enter the sftp server user name and password  here)
  • the password field will be encrypted and send the value to Map/Reduce script parameter.
Above steps are my approach to connect to sftp server.

So, overall here are the steps to follow:

1. create a saved search.
2. Generate a CSV file using the saved search results.
3. Create a CSV file and upload in filecabinet.
4. Generate the host key from SFTP server (in my case i have used "https://www.sftp.net/public-online-sftp-servers" link and used "test.rebex.net" sftp server which has the "read-only" permission.
Got the host key from same website, you can use ssh -keyscan hostname or password.
5. use the host key in the script where you use sftp connection.
6. Create a Suitelet script and generate GUID using the sftp server password.
GUID stands for Global Unique Identifier. This is a unique identifier generated in Oracle NetSuite by a particular algorithm. You can use this algorithm to generate a GUID for a specific domain and script.
7. Create the connection and upload the CSV file into SFTP server or download the file from sftp to NS file Cabinet.

Code:
Map/Reduce Script:
/**
 * @NApiVersion 2.x
 * @NScriptType MapReduceScript
 * @NModuleScope SameAccount
 * 
 * This script push the saved search results into a Folder in FileCabinet
 * From File Cabinet, script uploads file from filecabinet to SFTP Server.
 * Also, this script downloads a file from SFTP server to File Cabinet.
 * 
 */
define(['N/file', 'N/sftp', 'N/search', 'N/url', 'N/runtime'],
/**
 * @param {file} file
 * @param {search} search
 */
function(file, sftp, search, url, runtime) {
   
    /**
     * Marks the beginning of the Map/Reduce process and generates input data.
     *
     * @typedef {Object} ObjectRef
     * @property {number} id - Internal ID of the record instance
     * @property {string} type - Record type id
     *
     * @return {Array|Object|Search|RecordRef} inputSummary
     * @since 2015.1
     */
    function getInputData() {
    // Create Customer Payments Saved Search
    return search.create({
       type: "customerpayment",
       filters:
       [
          ["type","anyof","CustPymt"], 
          "AND", 
          ["mainline","is","F"]
       ],
       columns:
       [
          "entity",
          "statusref",
          "amountpaid"
       ]
    });

    }

    /**
     * Executes when the map entry point is triggered and applies to each key/value pair.
     *
     * @param {MapSummary} context - Data collection containing the key/value pairs to process through the map stage
     * @since 2015.1
     */
    function map(context) {
   
    var searchResult = JSON.parse(context.value);
   
    var transId = searchResult.id;
    var entityId = searchResult.values.entity.text;
    var status = searchResult.values.statusref.text;
    var amount = searchResult.values.amountpaid;
    //key, value pairing
    context.write(searchResult.id,entityId+','+status+','+amount);
    }

    /**
     * Executes when the reduce entry point is triggered and applies to each group.
     *
     * @param {ReduceSummary} context - Data collection containing the groups to process through the reduce stage
     * @since 2015.1
     */
    function reduce(context) {
   

    }


    /**
     * Executes when the summarize entry point is triggered and applies to the result set.
     *
     * @param {Summary} summary - Holds statistics regarding the execution of a map/reduce script
     * @since 2015.1
     */
    function summarize(context) {
   
    var contents = 'ENTITY ID'+','+'STATUS'+','+'AMOUNT\n';
    var totalRows = 0;
   
   
    context.output.iterator().each(function(key, value) {
           contents += value+'\n';
             totalRows++;
             return true;
          });
    //set the date and time
    var date = new Date();
   
    var dd = date.getDate();
    var mm = date.getMonth()+1;
    var yy = date.getYear()+1900;
    var time = date.getHours() +":"+date.getMinutes()+":"+date.getSeconds();
    //append the date and time values to the filename
    var file_name = "Customer Payments"+"_"+dd+"/"+mm+"/"+yy+"_"+time
    //Create a CSV file and place the search results into it
          var fileObj = file.create({
             name: file_name,
             contents: contents,
             folder: 12641,
             fileType: 'CSV'
          });
          var FileObjNum = fileObj.save();
          // Get the password GUID from the script parameter (**The GUID is created in Suitelet script) 
          var pwdVal = runtime.getCurrentScript().getParameter({name:'custscript_custom_password'});
          log.debug("pasword Value",pwdVal);
          var json = JSON.parse(pwdVal);
          log.debug("json data",json);
          var myPwdGuid = json.password;
          
          // Get the host key from the SFT server
          var hostKey = "AAAAB3NzaC1yc2EAAAABJQAAAQEAkRM6RxDdi3uAGogR3nsQMpmt43X4WnwgMzs8"
           hostKey += "VkwUCqikewxqk4U7EyUSOUeT3CoUNOtywrkNbH83e6/yQgzc3M8i/eDzYtXaNGcK"
           hostKey += "yLfy3Ci6XOwiLLOx1z2AGvvTXln1RXtve+Tn1RTr1BhXVh2cUYbiuVtTWqbEgErT"
           hostKey += "20n4GWD4wv7FhkDbLXNi8DX07F9v7+jH67i0kyGm+E3rE+SaCMRo3zXE6VO+ijcm"
           hostKey += "9HdVxfltQwOYLfuPXM2t5aUSfa96KJcA0I4RCMzA/8Dl9hXGfbWdbD2hK1ZQ1pLv"
           hostKey += "vpNPPyKKjPZcMpOznprbg+jIlsZMWIHt7mq2OJXSdruhRrGzZw=="
           
            var sftUrl = "test.rebex.net";
          // Connect the SFTP with the details
            var connection = sftp.createConnection({
                   username : 'demo',
                   passwordGuid : myPwdGuid,
                   url : sftUrl,
                   port : 22,
                   directory : "/",
                   hostKey : hostKey
                    });
          
          log.debug("Connection established successfully")
          
          log.debug("File Objet",FileObjNum); 
          /**
           * Downloading a ".txt" file from external SFTP server to File Cabinet.
           */
          var downloadedFile = connection.download({
              directory : '/pub/example',
              filename : 'readme.txt'
          });
          downloadedFile.folder = 12641;
          downloadedFile.save();
          
          log.debug("Downloaded Successfully)")
          
          /**
           * Uploading the file to the external SFTP server.
           */
          var myFileToUpload = file.load({
              id : FileObjNum       
              });     
          
          connection.upload({
              directory : '/aspnet_client/system_web',
              filename : file_name,
              file : myFileToUpload,
              replaceExisting : true
          });
          
          log.debug("File Uploaded successfully");
          
    }
    return {
        getInputData: getInputData,
        map: map,
       // reduce: reduce,
        summarize: summarize
    };
    
});

Suitelet:

/**
 * @NApiVersion 2.x
 * @NScriptType Suitelet
 * @NModuleScope SameAccount
 */
define(['N/ui/serverWidget', 'N/task', 'N/file','N/runtime'],
/**
 * @param {serverWidget} serverWidget
 */
function(serverWidget, task, file, runtime) {
   
    /**
     * Definition of the Suitelet script trigger point.
     *
     * @param {Object} context
     * @param {ServerRequest} context.request - Encapsulation of the incoming request
     * @param {ServerResponse} context.response - Encapsulation of the Suitelet response
     * @Since 2015.2
     */
    function onRequest(context,FileObjNum) {
    if (context.request.method === 'GET') {
   
    // Create a GUID form
    var form = serverWidget.createForm({

    title: "SFTP - GUID Form",

    });
   
    // add the username field
    form.addField({

    id: 'username',

    type: serverWidget.FieldType.TEXT,

    label: 'Username'

    });
    // add password field
    var credField = form.addCredentialField({

    id: 'password',

    label: 'Password',
    //make sure you mention the script where SFTP connections takes place
    restrictToScriptIds: 'customscript_nf_mr_uploadcsv',

    restrictToDomains: "test.rebex.net"

    });
   
    credField.maxLength = 64;
    //Create a Submit button
    form.addSubmitButton({

    label: 'Submit Button'

    });
    context.response.writePage(form);

    return;
    }else {

    var requset = context.request;

    var myPwdGuid = requset.parameters.password;

    log.debug("myPwdGuid", myPwdGuid);

    context.response.write(myPwdGuid);

    }
   
    var objParam = JSON.stringify({"password": myPwdGuid});
    //Create task to call Map/Reduced
    var mrTask = task.create({
    taskType: task.TaskType.MAP_REDUCE,
    scriptId: "customscript_nf_mr_uploadcsv",
    deploymentId: "customdeploy1",
    params: {'custscript_custom_password': objParam} //use the MapReduce script parameter internalid here
    });
   
    var mrTaskId = mrTask.submit();
    var taskStatus = task.checkStatus(mrTaskId);
    log.debug("taskStatus", taskStatus);
           
    }

    return {
        onRequest: onRequest
    };
    
});

Output Screenshots:

1.GUID - Generation screen - from Suitelet
2. Create file in file cabinet and check the download file from sftp server.

3. Check the log whether connection established successfully or not and downloaded the file successfully or not


4.  Check the .txt file in sftp server which is downloaded successfully.

Unfortunately I don't have proper SFTP server where i cannot upload the file in file sftp server, but able to download the file into file cabinet. I'm sure this code will definitely work if we have proper sftp server with all permissions.

Note: If any issue like permission issue, like failed to upload then system will throw below error:
{"type":"error.SuiteScriptError","name":"UNEXPECTED_ERROR","message":null,"stack":["anonymous(N/file)","summarize(/SuiteScripts/Ram Suite Script files/_nf_uploadcsv_mr.js:160)"],"cause":{"type":"internal error","code":"UNEXPECTED_ERROR","details":null,"userEvent":null,"stackTrace":["anonymous(N/file)","summarize(/SuiteScripts/Ram Suite Script files/_nf_uploadcsv_mr.js:160)"],"notifyOff":false},"id":"c66e19b0-046f-4c01-887c-26e8a6e8724e-2d323031392e31302e3135","notifyOff":false,"userFacing":false}

But, please follow my suggestions, you will be able to upload the file in file cabinet.

Also, suggest me if you have any opensource sftp connection details including hostkey, i'll try to connect.

Thanks,
Ramu

Tuesday 17 September 2019

Understand how to validate the Address record using suite Script?

In NetSuite, Address is a subrecord which is placed in the address subtab.


Now, i wanted to make "Phone" is mandatory.

Here is the process how can we go-ahead:

1. Login to NetSuite, Go to Customization -> Forms -> Address Forms.
2. Create a custom address form.
3. Make sure create a script to validate the phone number field, (Use only suitescript 1.0)

Sample code is below: (Used Client Script)

function ValidateAddress(type){

var phNum = nlapiGetFieldValue('addrphone');
if ((phNum == "") || (phNum == null)) {
alert ("Please enter the Phone number");
}

    return true;
}

4. Now go back to the custom address form, go to Custom code subtab, "Script File" select the
script, which is created in above step. Attach the script here like :
   
        In the script file field -> click on the "Plus(+)" button, a window popups:


5. Select the script file and close the window.
6. under the "Validate Field Function" field, select the function from the suitescript function where we had the validation logic (ex: i have mentioned ValidateAddress).

7. Save the form and the customization is completed.

Now, go to address subtab in employee, click on the pencil icon, the address subrecord will be opened, now click on the "Ok" button. the alert populates:



Thanks,
Ramu






Thursday 22 August 2019

What is SuiteAnalytics Connector ? how can we establish a connection between Netsuite and MS PowerBI using the suite analytics connector?


Suppose if you want to connect NS with any 3rd paty Data analytics applications, we can do with “Suite Analytics connector”.

 Connect Service, lets you archive, analyze, and report on NetSuite data using a third-party tool or any custom-built application on any type of device using a Windows, Linux, or OS X operating system.
Once the Connect Service is enabled, then NetSuite offers ODBC, JDBC, and ADO.NET drivers that you can download, install, and use to connect to the Connect Service.

Note: The SuiteAnalytics Connect Service provides a read-only method for obtaining NetSuite data. You cannot use the Connect Service to update NetSuite data.

(There are many different ways that we can connect, but what I followed is using ODBC drivers, if you need more details I prefer you to go through Netsuite help docs)


To connect the Suite Analytics connector please follow below process:  (as administrator)
1. Login to NetSuite , Go to Setup -> Enable Features -> Analytics -> enable the “Suite Analytics Connector” checkbox and save it.
2. Go to Home page -> Settings portlet -> click on “Set Up SuiteAnalytics Connect”.

3.    Once clicked on Set Up Suite Analytics connect, then you will be navigated to “SuiteAnalytics Connect Driver Download “ page.

You can select the required bundle for the OS (windows/Linux).
In the example I have downloaded 64bit “ODBC INSTALLATION BUNDLE 64-BIT (7.20.55)” bundle.


4. Once the download is complete then you can see the "NetSuiteODBCDrivers_Windows64bit" zip file in the download folder.

5. Extract the "NetSuiteODBCDrivers_Windows64bit", and double click on the highlighted application.




6. the below window will be opened and click on the Next button.



7. Once Next button is clicked, then accept the license.


8. Once license is accepted, then you will be displayed with Datasource configuration details. Now, enter the configuration details like which are displayed in step 3.


9. Finish the installation process.

This way ensures that the ODBC setup is completed in the windows system.

Once installation completes we can see the connection details in Program Files.




Now, connecting with MS Power BI (I'm going through the steps after installing the MS Power BI)

1. Go to MS Power BI application, go to File -> Get Data -> Click on "More"

2. Go to Other and click on "ODBC" in the pop up window


3. Double click on the ODBC, and you will be navigated to "From ODBC" pop up window, select "NetSuite" in the drop down.


4. Once clicked on "OK" button, Navigator window will be open, select the data what ever you load in the Power BI.



5. Click on the Load button once you select what data you want to visualize.


This ensures the connection establishes between netsuite and power bi.

Like this we can connect any other analytics application.

Again, i'm repeating if you want more details about suite analytics connector please go though the suiteAnswers.

Thanks,
Ramu

Saturday 27 July 2019

How to access Sublist using Workflows ?

Accessing Sublist using workflows is pretty much possible using Suiteflow. 

As per the draft version from release notes of 2019.2 is :
 you can set the following workflow actions to execute on supported sublists:

■ Return User Error
■ Set Field Value
■ Set Field Display Type

This enhancement increases the functionality on sublists and allows for greater customization of workflows. For an overview of SuiteFlow actions, including action properties, triggers, and conditions, see the help topic Workflow Actions.
To set an action to execute on a sublist, in a workflow, select the state that you want to add the action to. Click the New Action icon in the context panel. In the New Action window, click an action. In the Triggering Client Fields section of the Workflow Action window, choose Sublist, and complete the parameters and conditions section of the action. When you are done entering the action details, click Save. For more information, see the help topic Creating an Action.

For more details, please download the release notes from SuiteAnswers.

Tuesday 23 July 2019

how to implement Token based authentication in NetSuite ?


Token Based Authentication in NetSuite:


NetSuite supports token-based authentication (TBA) a robust, industry standard-based mechanism that increases overall system security. 

This authentication mechanism enables client applications to use a token to access NetSuite through APIs, eliminating the need for RESTlets or web services integrations to store user credentials.

Tokens can be used instead of usernames and passwords.

Benefits of  using the TBA:

     -  Safer and Easier to manage.
2         -  Programmatically can create Tokens.
3         - Password rotation policies don’t apply to Tokens, like if any password that is expired then you  don’t               need to worry about it

.Below are the steps which needs to be followed to setup TBA in NetSuite:

1             
        -  Enable the Token Based Authentication feature in NetSuite.
2      -  Setup the TBA roles with permissions.
3      -  Assign the TBA Roles to the users.
4      -  Setup applications for Token based authentications.
5      -  Create User Tokens.

Here is how to implement in detail:

      -  Enable the Token Based Authentication feature in NetSuite.

N 1. Navigate to Enable Features page: 
s     Setup -> Company -> Enable Features:
       
      Under Suite cloud Tab -> Under Manage Authentication Section -> Select "Token Based Authentication.
     It will as you to take action on "Terms of Service" , select "I agree" button. Then click on save button.


2. Setup the TBA roles with permissions.

Once the service is enabled, we can assign below Token-based Authentication Permissions to any role: (Except Administrator)

Access Token Management
  • Users (Non Administrator role) with this role can create and revoke access tokens for user with TBA role enabled. 
  • A user who doesn't have "Administrator" role cannot create tokens for administrator.
  • Cannot create access for their own use.
  • Cannot use access tokens to login through RESTLets or WebServices.     


       User Access Tokens
  •  Users with this permission can manage their own tokens using the Manage Access Tokens link in the Settings portlet, and they can log in using a token.
  • Users can use access to login through RESTLets or WebServices.
Log in using Access Tokens
  • Users with only this permission can log in using a token, that is, they can to use tokens to call a RESTlet.
  • Cannot create their own access tokens through a link in the Settings portlet, or by calling the token endpoint.
Create a new Role and assign TBA permissions to it:

Go to Setup -> Users/ Roles - > Manage Roles -> New

Go to the role in create/Edit mode, Under Permissions Tab: Select the any of the TBA permissions as per the requirement and save the role.



Assign the TBA Roles to the users:

Now, after creating the role, then go to the employee record to whom you want assign, 
Lists -> Employees -> List -> Select the employee (Select New if you want to create):

In Access Subtab - > Roles -> Add the newly created role, and save the employee record.



Setup applications for Token based authentications.

             We can see all the Integration applications in the below navigation:
              Setup -> Integrations -> Manage Integrations -> List (we can see all the 3rd party integration applications list)

           For our test, we are creating new Integration, like below:

If the State is Enabled means:  if you want to permit connections from the external application represented by this integration record. 

If the State is Blocked means : if you want to prevent such connections.

Click on the Token-Based Authentication check box under "Authentication" subtab and save the integration record.



Once the Integration is saved then the "Application ID" will be generated automatically.

And under "Authentication" section, We can see "Consumer Key" and "Consumer Secret".

For Security reasons the Consumer key/Secret displays only one time. If suppose you forget the the key then you will need to reset them to obtain new values.

Create User Tokens:

We can assign user Tokens with below navigation:

               Setup -> Users/Roles -> Access Tokens -New
\
       Select the Application which needs to be integrated, then select the user (it will list the employee name who has the TBA roles), select the TBA role, Token Name field is populated with a concatenation of Application Name, User, and Role. Enter your own name for this token, if desired.

After saving this record, system will generate automatically a Token ID and Token Secret which will only displayed for one time.
            
       Creating own Tokens:
        
                 Go to Home -> Settings portlet -> Manage Token (here we can see list of the my access tokens ).





























Sunday 14 July 2019

Understand CTA report in netsuite


CTA – Report - Cumulative Translation Adjustment Report

The cumulative translation adjustment (CTA) for a currency translation adjustment is an entry in the “Accumulated Other Comprehensive Income” section of the translated balance sheet, reflecting gains and losses caused by exchange rate fluctuations over the years.

Understand CTA Account:
-          Cumulative Translation Adjustment (CTA) is a special type of account that is required for consolidated balance sheets in NetSuite OneWorld accounts with multi-currency enabled.

-          The CTA is used on the consolidated balance sheet to make it balance despite differing foreign exchange rate types.
-          In NetSuite it is configured as “Equity” type.
-          No Currency is defaulted.
-          General Rate Type – Historical, Cash flow Rate – Historical.

Understand the Exchange Rates from Consolidated Exchange Rates:

Average – This rate is calculated from a weighted average of the exchange rates for transactions applied during the period to accounts with a general rate type of Average.
Current – Also referred to as ending rate. This rate is based on the currency exchange rate that is effective at the end of the reported upon period.
Historical – This rate is calculated from a weighted average of the exchange rates for transactions applied during the period to accounts with a general rate type of Historical.
  • General Rate Type:
This General Rate Type uses in all in the income statement, balance sheet, and other general purposes.
    • Current - for all balance sheet accounts other than equity accounts
    • Average - for all income statement accounts
    • Historical - for all equity accounts
  • Cash Flow Rate Type:
This Cash Flow Rate Type uses in all Cash Flow statements.
    • Average - for all accounts
So, here the CTA account is “Equity” type, hence it has the General Rate Type as “Historical” and Cash Flow Rate Type as “Average”.

  
Example:
If a U.S.-based company wishes to operate in Germany, it must convert some of its U.S. dollars to euros for purposes of purchasing or renting property, paying employees, paying German taxes, etc. In addition, German citizens or businesses that work with this U.S.-based company will pay with euros. The company will create its financial statements in one currency, the dollar. It must convert the value of its business activities conducted in Germany with the euro back to dollars via an exchange rate.

Key points of CTA report in NetSuite:

1.       This report is available only for OneWorld accounts with the Multiple Currencies and Accounting features enabled. 
2.       The Financial Statements permission is required to access the report.
3.       The CTA Balance Audit report shows the contribution from individual accounts to the CTA during the selected period. 
4.       The rows in the CTA Balance Audit report follow the same order as the rows of the Balance Sheet.
5.       The CTA represents the cumulative foreign currency gain or loss resulting from the net investment in the subsidiary.

The CTA – Balance Audit Report has below columns:

1.       Starting Balance Sheet (As of <period>) – The period is the one before the period selected in the From field in the footer.
Local Balance – Balance of the ledger account till the period end (as per this last period end)
General Rate – Rate Type used to convert exchange rate
Consolidate Balance – This is equal to the Local Balance multiplied by the General Rate. The total in this column ties to the CTA amount in the Balance Sheet for the period before the one selected in the From field in the footer.

Example: if you are running CTA report for July month, then Starting Balance sheet shows till June End.
2.     Net Posting (<period>) – The period is the period or range of periods set in the From and To fields in the footer.
Local Balance: Current period balance (based on From and To fields)
Cash Flow Rate: It has the exchange rate value which converts based on CTA account configuration.  (in my account it is historical type)
General Rate: : It has the exchange rate value which converts based on CTA account configuration.  (in my account it is historical type)
Consolidated Balance – This is equal to the Local Balance multiplied by the General Rate.
Example : This has the current period balance based on the From and To Fields selected in the report.
Ending Balance Sheet (As of <period>) – The period is the one set in the To field in the footer.
o    Local Balance: this ledger has the last period ending balance + current period ledger balance.
o    General Rate: It has the exchange rate value which converts based on CTA account configuration.  (in my account it is historical type)
o    Consolidated Balance – This is equal to the Local Balance multiplied by the General Rate. The total in this column ties to the CTA amount in the Balance Sheet for the ending period in the range of periods.

CTA Analysis
o    Beginning Balance Rate Difference Contribution – This equals the Beginning Balance Translated at Ending Implied Rate minus Starting Balance Sheet Consolidated Balance.
o    Net Posting Rate Difference Contribution – This equals Net Posting Consolidated Balance minus Net Posting Consolidated Balance at Cash Flow Rate Contribution.
o    Net Posting Consolidated Balance at Cash Flow Rate Contribution – This equals Net Posting Local Balance multiplied by the Net Posting Cash Flow Rate.
o    Total CTA Contribution – Beginning Balance Rate Difference Contribution plus Net Posting Rate Difference Contribution plus Net Posting Consolidated Balance at Cash Flow Rate Contribution

To run the report – Go to Reports -> Financial -> click on CTA Balance Audit report.


I would like to give credits to NetSuite, for the wonderful documentation provided, it helped me a lot understand the report. For more information i would prefer, SuiteAnswers.

Thanks,
Ramu