Closing a Sales Order from a Button
Here is a use-case example, where it's desired to add a button allowing the user to close a sales order in NetSuite right from the Sales Order in Salesforce.
This can be done by using a Flow Button which kicks off a Flow and simply passes the source Sales Order record to an Invokable Apex Action which does all the work.
Let's do this and also set up to receive back the API message from NetSuite to see if the process was successful. This is not necessary, but shown in case it's useful. Since we'll be getting a NetSuite response back, we will NOT use the Future method callout.
First things first, set up the Apex Class to close Sales Orders. Input parameter (in Request section) has the description 'NetSuite Sales Order' which will come up on the flow parameters list. The Output parameter (in Response section) has the description 'netSuiteResponse' and we will be able to take the value and display it to screen.
public with sharing class closeSalesOrder_Invokable {
public closeSalesOrder_Invokable() {
}
@InvocableMethod(
label='Close NetSuite Sales Order'
description='Close NetSuite Sales Order'
category='Breadwinner'
)
public static List<Response> closeSalesOrder(List<Requests> requestList) {
Response response = new Response();
Id salesOrderId = requestList[0].salesOrder;
System.debug('SO ID ::: ' + salesOrderId);
Map<Id, breadwinner_ns__BW_Sales_Order__c> salesOrdersById = new Map<Id, breadwinner_ns__BW_Sales_Order__c>(
[SELECT Id, breadwinner_ns__InternalId__c from breadwinner_ns__BW_Sales_Order__c where Id = :salesOrderId]
);
System.debug('SO By ID ::: ' + salesOrdersById);
Map<Id, breadwinner_ns__BW_Line_Item__c> lineItemsById = new Map<id, breadwinner_ns__BW_Line_Item__c>(
[SELECT Id, breadwinner_ns__ItemInternalId__c, breadwinner_ns__Line__c, breadwinner_ns__IsClosed__c from breadwinner_ns__BW_Line_Item__c where breadwinner_ns__Sales_Order__c = :salesOrderId]
);
System.debug('LI By ID ::: ' + lineItemsById);
//Create SalesOrder map for SO values query
Map<String, Object> nsSalesOrder = new Map<String, Object>();
for(breadwinner_ns__BW_Sales_Order__c salesOrdersByIdValue: salesOrdersById.values()){
nsSalesOrder.put('internalId', salesOrdersByIdValue.breadwinner_ns__InternalId__c);
}
//Initialize Sales Order Line Items
List<Object> lineItemList = new List<Object>();
for(breadwinner_ns__BW_Line_Item__c lineItemValue: lineItemsById.values()){
Map<String, Object> lineItem = new Map<String, Object>();
lineItem.put('item', new Map<String, Object>{'internalId'=>lineItemValue.breadwinner_ns__ItemInternalId__c});
lineItem.put('line', lineItemValue.breadwinner_ns__Line__c);
lineItem.put('isClosed', true);
lineItemList.add(lineItem);
}
System.debug('Line Item Updates ::: ' + lineItemList);
// Adding the Line Items to Sales Order
Map<String, Object> soLineitemList = new Map<String, Object>();
soLineitemList.put('item',lineItemList);
soLineitemList.put('replaceAll', false);
nsSalesOrder.put('itemList',soLineitemList);
// Initialize the RequestJSON Data to be passed.
Map<String, Object> requestJSONMap = new Map<String, Object>();
requestJSONMap.put('salesOrders', new List<Object>{nsSalesOrder});
String reqJSON = JSON.serialize(requestJSONMap);
Map<String, Object> reqObj = new Map<String, Object>();
reqObj.put('version', '1.0');
reqObj.put('action', 'updateSalesOrder');
reqObj.put('requestJSON', reqJSON);
System.debug('Request ::: ' + reqObj);
// Place a request to Breadwinner Global API
Map<String, Object> respMap = breadwinner_ns.BreadwinnerNetSuiteAPI.call(reqObj);
System.debug('Response ::: ' + respMap);
//Take the NetSuite response map and convert to single string
String nsResponseString = '';
for(String key : respMap.keySet()){
if(nsResponseString.length() !=0){
nsResponseString += '::';
}
nsResponseString += key + '=' + respMap.get(key);
}
response.netSuiteResponse = nsResponseString;
List<Response> responseWrapper= new List<Response>();
responseWrapper.add(response);
return responseWrapper;
}
public class Requests {
@InvocableVariable(label='NetSuite Sales Order' description='NS Sales Order (SF ID)') public Id salesOrder; //lookup
}
public class Response {
@InvocableVariable(label='Error' description='Error') public String error;
@InvocableVariable(label='netSuiteResponse' description='API Response from NetSuite') public string netSuiteResponse;
}
}With the APEX class now loaded to Salesforce, let's build the flow. Keeping it simple we will just read the current record we're on (user will be interacting from the NetSuite Sales Order) and pass the ID to the class to close it.



Creating a new variable called recordId and setting it's default value to {!$Flow.CurrentRecord} is used to easily refer to the source record (NetSuite Sales Order) the user is looking at.
After Creating recordId TEXT variable, create another TEXT variable called NetSuiteResponse. That will get the reply from the NetSuite API so it can be displayed for debugging.
And finally add the APEX Action from the code that was added to Salesforce.


The action we want can be found in the Breadwinner category (set in the description and category section of Apex Class)

Fill out the parameters, and don't forget to open up the Advanced section for reply values

Great! Now the APEX invokable class is installed, and we've got a Screen Flow built to run it. Let's DEBUG to make sure it works. Click DEBUG on the flow and feed it the record id from a test NetSuite Sales Order you want to close:




Click Next or Finish and the APEX will fire off and close that order. The response might be a bit messy looking, but there's alot of good debug info in there in case something doesn't go as planned.


With the flow having been run, check out the Sales Order that you processed


And there we go! We've closed a sales order using a Flow in Salesforce. From here you can deploy the flow to Salesforce by creating a new Apex Action button and add it to your page layout.