Reopening NetSuite Work Orders

NetSuite Work Orders have a few different status depending on the fields Buildable and Built quantity:

  • “Released” (when the Built quantity is 0),
  • “In Progress” (when Built quantity > 0 but not equal to the Buildable quantity) and
  • “Closed” (when Built quantity = Buildable)

NetSuite Work Orders can also be closed via the blue Close button in the UI.  This last can be problematic if there is still work to be performed in the Work Order.  Reopening a Work Order is the same as any NetSuite Transaction:  Uncheck the IsClosed column of one or more Work Order line items.   The problem is there is no way to do this in the UI.  Instead, we can run SuiteScript in the Script Debugger (Customization | Scripting | Script Debugger) to update the line items.  In the gist below, we load the workorder by ID, set each line item’s isclosed property to False and commit the record to the database.  The last nlapiLoadRecord() call is just so we can examine the record is how we expect.


var o = nlapiLoadRecord("workorder", 2433031);
for (var i = 1; i <= o.getLineItemCount("item"); i++)
o.setLineItemValue("item", "isclosed", i, "F");
var stat = nlapiSubmitRecord(o);
o = nlapiLoadRecord("workorder", 2433031);
var x = 1;

Do be careful because even though the debugger is its own domain it will change data in your production instance so use this cautiously!

How To: Parse a JSON response using Salesforce’s JSONParser class

Information sharing between systems is critical to business agility with REST and SOAP being the two main standards used.  With REST, responses are JSON encoded so Salesforce designed the JSONParser library.  When looking for example usage of this library I found some blog posts that simply use the readValueAs() method.  This is very convenient to parse well structured data into a class, but you don’t always get such nicely structured responses.  For example, this endpoint on geonames.org returns:


{"postalcodes":[
{"adminName2":"Olsztyn","postalcode":"10-509","countryCode":"PL","lng":20.4833333,"placeName":"Olsztyn","lat":53.7833333,"adminName1":"Warmińsko-Mazurskie"},
{"adminName2":"Putnam","adminCode2":"079","postalcode":"10509","adminCode1":"NY","countryCode":"US","lng":-73.599179,"placeName":"Brewster","lat":41.409704,"adminName1":"New York"},
{"postalcode":"10509","countryCode":"DO","lng":-70.24743589230769,"placeName":"Carmen Amelia ","lat":18.721794876923077},
{"postalcode":"10509","countryCode":"DO","lng":-70.24743589230769,"placeName":"La Agustinita ","lat":18.721794876923077},
{"adminName2":"La Magdalena Contreras","adminName3":"Ciudad de Mexico","adminCode2":"9008","postalcode":"10509","adminCode1":"DIF","countryCode":"MX","lng":-98.99779722222222,"placeName":"Residencial San Carlos","lat":19.20726277777778,"adminName1":"Distrito Federal"}
]}

view raw

JSON Return

hosted with ❤ by GitHub

As can be seen, results always contain the “postalcode”, “countryCode”, and “placeName” (among others) fields.  But the fields “adminName2” and “adminName1” may or may not appear.  This is inconvenient because readValueAs() won’t work properly so I needed to manually parse the response.  Salesforce’s JSON library example and this developer blog post helped my understanding, but I was still left wondering what methods to use and how. To figure this out, I ended up printing every token the JSONParser encountered and its corresponding text.  So, I wrote up my findings hoping to save you from that tedious task!

Firstly, in the JSON response, every character except the colon (:) is a token – unless it’s encapsulated in double quotes (“). Within double quotes, the entire string is the token.  So, many parsing routines traverse the JSON response with:

while (parser.nextToken() != null) {

Secondly, getCurrentToken() returns type JSONToken enum and getText() method returns the character(s) of that token.  So the calling getCurrentToken() at the very start of the JSON above returns START_OBJECT and getText() returns “{”

Thirdly, from the documentation we know:

  • { and } are identified by the tokens START_OBJECT and END_OBJECT, respectively
  • [ and ] are identified by the tokens START_ARRAY and END_ARRAY, respectively

I’ll also add FIELD_NAME is the token on the left side of a colon and the token to the right of a colon is VALUE_xx (e.g. VALUE_STRING, VALUE_TRUE, VALUE_NULL, etc).

Let’s look at this example:

“countryCode”:”PL”

Calling getCurrentToken() returns FIELD_NAME and getText() returns countryCode.  If nextToken() is called then getCurrentToken() returns VALUE_STRING and getText() returns PL

Enough theory already!  Let’s look at this code snippet deserializing the JSON response (cribbed from my post More Visual force zip code lookup post).


JSONParser parser = JSON.createParser(res.getBody());
System.JSONToken token;
string text;
parser.nextToken(); // Eat first START_OBJECT {
parser.nextToken(); // Eat token = FIELD_NAME; text = postalcodes
parser.nextToken(); // Eat first START_ARRAY [
parser.nextToken(); // Eat the first object's START_OBJECT {
while((token = parser.nextToken()) != null) {
// Parse the object
if ((token = parser.getCurrentToken()) != JSONToken.END_OBJECT) {
text = parser.getText();
if (token == JSONToken.FIELD_Name && text == 'countryCode') {
token=parser.nextToken();
z.setzipCountry(parser.getText());
found = true;
} else if (token == JSONToken.FIELD_Name && text == 'adminCode1') {
token=parser.nextToken();
z.setzipState(parser.getText());
found = true;
} else if (token == JSONToken.FIELD_Name && text == 'placeName') {
token=parser.nextToken();
z.setzipCity(parser.getText());
found = true;
} else if (token == JSONToken.FIELD_Name && text == 'postalcode') {
token=parser.nextToken();
z.setzipZip(parser.getText());
found = true;
}
} else { // Got to an end object so append it to the list
if (found) { // Only append to list if we extracted info we wanted.
z.setzipIndex(zipListIndex);
zipListIndex ++;
zips.add(z); // add a reference to the list
z = new zipInfo(); // allocate more memory to store the next object
found = false; // reset
}
// advance to next object
token = parser.nextToken();
if (token == JSONToken.END_ARRAY) { // we reached end of array of objects representing zipcodes
break;
}
}
}

view raw

JSONParserSrc1

hosted with ❤ by GitHub

  • Line 1 initializes the parser to the start of the JSON stream (the first “{“).
  • Lines 6 – 9 skip through the JSON stream until the deserializer points inside the first object (adminName2 in this case).
  • Lines 11 – 46 loop through the JSON stream until I run into an END_OBJECT.  When END_OBJECT is found I’ve traversed one object’s data in the response.
    • Within the loop, lines 14 – 31 check if the token is a FIELD_NAME and matches the desired info.  This is safe because we’re inside an object so we will only find FIELD_NAMEs in this example.
    • Lines 33 – 39 appends the data into my zips variable for display in the VF page’s pageBlockTable.  A couple of notes:
      1. The variable found indicates a valid result to display to the user. This way I don’t create empty entries in zips.  This only occurs if no results were found for a user’s input.
      2. zipListIndex identifies the radio buttons on the VF page.  So, the user selected radio button identifies which location they chose.  To see how all this in action refer to my previous post.
    • Lines 41 – 45 skip the current END_OBJECT token and breaks out of the loop if the array’s end is reached.

So, that’s how I used the JSON Parser library to deserialize a JSON response.  While not a very complex example, it’s useful to illustrate the basics of operation.  If I find a more sophisticated response I’ll post about it.

More VisualForce zip code lookup

I wrote two prior How To’s doing zip code lookups in a VisualForce page and in a trigger.  This post expands on the zip code lookup functionality using geonames web services because the service I used previously, ziptasticapi.com, only supports zip codes in the United States.  Geonames.org also offers richer results and additional webservices beyond postal code lookup.

Creating a free account on geonames.org gives a generous limit of 30k API calls per day or 2k calls per hour.  As with all freemium models there’s also a paid subscription with better SLAs.  One complication is the website returns more hits (unless filtered) and the format of the results have more variability.  This requires a deeper understanding of Salesforce’s JSON Parser beyond the readValueAs() method.  For example,  lookup “10509” on geonames yields the below return.  More on this in another post.

{"postalcodes":[
{"adminName2":"Olsztyn","postalcode":"10-509","countryCode":"PL","lng":20.4833333,"placeName":"Olsztyn","lat":53.7833333,"adminName1":"Warmińsko-Mazurskie"},
{"adminName2":"Putnam","adminCode2":"079","postalcode":"10509","adminCode1":"NY","countryCode":"US","lng":-73.599179,"placeName":"Brewster","lat":41.409704,"adminName1":"New York"},
{"postalcode":"10509","countryCode":"DO","lng":-70.24743589230769,"placeName":"Carmen Amelia ","lat":18.721794876923077},
{"postalcode":"10509","countryCode":"DO","lng":-70.24743589230769,"placeName":"La Agustinita ","lat":18.721794876923077},
{"adminName2":"La Magdalena Contreras","adminName3":"Ciudad de Mexico","adminCode2":"9008","postalcode":"10509","adminCode1":"DIF","countryCode":"MX","lng":-98.99779722222222,"placeName":"Residencial San Carlos","lat":19.20726277777778,"adminName1":"Distrito Federal"}]}

Visual Force Page

The VisualForce page looks similar to the previous example but lists results in a table.  In addition, logic is added so users can choose one of the results via a radio button.  See the below screenshot.

Zip code lookup results

Zip code lookup results

The VisualForce page source code is


<apex:page controller="zipcodeGeonames">
<apex:form >
<apex:pageBlock >
<apex:actionRegion id="lookupRegion" >
<apex:outputLabel value="Enter 5 digit zip code " for="inputzip" />
<apex:inputText value="{!zipValue}" id="inputzip" />
<apex:outputLabel value="Enter Country " for="inputCountry" />
<apex:inputText value="{!inputCountry}" id="inputCountry" />
<!– The rerender attribute causes addressList to be re-rendered when the button is clicked –>
<apex:commandButton action="{!lookupList}" value="Lookup!" rerender="addressList" />
</apex:actionRegion>
<apex:pageBlockSection >
<apex:pageBlockTable id="addressList" var="z" value="{!zipList}" >
<apex:column >
<apex:facet name="header">Zip Code</apex:facet>
<input type="radio" name="Postal Lookup" value="{!z.zipCount}" onclick="javascript:sendZipSelection(this.value);" />
{!z.zipZip}
</apex:column>
<apex:column >
<apex:facet name="header">Country</apex:facet>
{!z.zipCountry}
</apex:column>
<apex:column >
<apex:facet name="header">City</apex:facet>
{!z.zipCity}
</apex:column>
<apex:column >
<apex:facet name="header">State</apex:facet>
{!z.zipState}
</apex:column>
</apex:pageBlockTable>
</apex:pageBlockSection>
</apex:pageBlock>
<!– Must add rerender attribute to work correctly. See: http://salesforce.stackexchange.com/questions/29400/calling-apexactionfunction-on-apexcommandbuttons-onclick-event
and https://help.salesforce.com/apex/HTViewSolution?id=000002664&language=en_US –>
<apex:actionFunction name="sendZipSelection" action="{!zipSelection}" rerender="content" >
<apex:param name="myParam" value="" />
</apex:actionFunction>
</apex:form>
</apex:page>

view raw

gistfile1.txt

hosted with ❤ by GitHub

Breaking down the page we see that:

  • Lines 5 – 12 which contains the apex:actionRegion is nearly the same as the first version.
  • Lines 15 – 35 use the apex:pageBlockTable component to list the returned array of results.
  • Lines 37 – 43 use apex:actionFunction to call the controller’s sendZipSelection() method with the user’s selection.

One interesting detail is <input type = “radio”> on line 19.  Since VisualForce supports HTML pass-thru, we can use HTML input tags to create radio buttons.  At first, I tried the apex:selectRadio component but the radio buttons only showed horizontally which was visually unappealing.  Subsequently, I discovered layout=”pageDirection” attribute which organizes radio buttons vertically, but I digress…  The onclick attribute of the input tag invokes the actionFunction component to call the controller’s sendZipSelection() method with the number of the user selected radio button (z.zipCount) as the parameter.

Controller Code

The controller has evolved to support the new requirements.


public with sharing class zipcodeGeonames {
// Constructor
public zipCodeGeonames() {
zipValue = '';
inputCountry = '';
zips = new List<zipInfo>();
}
public class zipCodeException extends Exception {}
// zipValue property is used by VF page and is the zipcode entered by user (required)
public string zipValue {
get { return zipValue; }
set { zipValue = value; }
}
// Country input by user (optional)
public string inputCountry {
get { return inputCountry; }
set { inputCountry = value; }
}
// VF page calls this with the user's selection. If this example is used in an app then this function interface to the remainder of the app
public void zipSelection() {
string value = Apexpages.currentPage().getParameters().get('myParam');
system.debug('zipSelected is '+Apexpages.currentPage().getParameters().get('myParam'));
}
public List<zipInfo> zips;
public zipInfo z = new zipInfo();
public List<zipInfo> zipList {
get {
return zips;
}
set { zipList = value; }
}
// This class holds the details for each zipcode looked up
public class zipInfo {
private string zip;
private string city;
private string state;
private string country;
private integer zipIndex;
// Properties for VF page
public string zipCity {
get {return city;}
}
public string zipZip {
get {return zip;}
}
public string zipState {
get {return state;}
}
public string zipCountry {
get {return country;}
}
public integer zipCount {
get {return zipIndex;}
}
// Public functions to set private member variables
public void setzipCity(string value){
city = value;
}
public void setzipZip(string value){
zip = value;
}
public void setzipState(string value){
state = value;
}
public void setzipCountry(string value){
country = value;
}
public void setzipIndex(integer value){
zipIndex = value;
}
}
// Helper function to print 'zips' (the zipcode list)
private void printZipList()
{
integer counter = 0;
while (zips.size() > counter) {
system.debug('zips['+counter+'] = '+zips[counter]);
counter++;
}
}
public PageReference lookupList()
{
boolean found = false;
integer zipListIndex = 0;
zips.clear(); // Remove results from previous lookups
string geonamesEndpoint = 'http://api.geonames.org/postalCodeLookupJSON?username=scotthung&postalcode='+zipValue;
if (inputCountry.length() > 0) {
geonamesEndpoint = geonamesEndpoint + '&country='+inputCountry;
}
system.debug(LoggingLevel.Error, 'Calling endpoint='+geonamesEndpoint);
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setMethod('GET');
req.setEndpoint(geonamesEndpoint);
try {
res = http.send(req);
if (res.getStatusCode() != 200) {
throw new zipCodeException(res.getStatus());
}
system.debug(LoggingLevel.Error,'res.body='+res.getBody());
} catch (zipCodeException e) {
system.debug(LoggingLevel.Error, 'Error HTTP response code = '+res.getStatusCode()+'; calling '+geonamesEndpoint );
return null;
}
JSONParser parser = JSON.createParser(res.getBody());
System.JSONToken token;
string text;
parser.nextToken(); // Eat the first {
parser.nextToken(); // eat the postalcodes token
parser.nextToken(); // eat the array [
parser.nextToken(); // eat the START_OBJECT {
while((token = parser.nextToken()) != null) {
// Parse the object
if ((token = parser.getCurrentToken()) != JSONToken.END_OBJECT) {
text = parser.getText();
if (token == JSONToken.FIELD_Name && text == 'countryCode') {
token=parser.nextToken();
z.setzipCountry(parser.getText());
found = true;
} else if (token == JSONToken.FIELD_Name && text == 'adminCode1') {
token=parser.nextToken();
z.setzipState(parser.getText());
found = true;
} else if (token == JSONToken.FIELD_Name && text == 'placeName') {
token=parser.nextToken();
z.setzipCity(parser.getText());
found = true;
} else if (token == JSONToken.FIELD_Name && text == 'postalcode') {
token=parser.nextToken();
z.setzipZip(parser.getText());
found = true;
}
} else { // Got to an end object so append it to the list
if (found) { // Only do this if we extracted info we wanted.
z.setzipIndex(zipListIndex);
zipListIndex ++;
zips.add(z); // add a reference to the list
z = new zipInfo(); // allocate more memory to store the next object
found = false; // reset
}
// advance to next object
token = parser.nextToken();
if (token == JSONToken.END_ARRAY) { // we reached end of array of objects representing zipcodes
break;
}
}
}
return null;
}
}

view raw

gistfile1.cls

hosted with ❤ by GitHub

Breaking down the controller code:

  • Lines 13 – 21 are properties to accept user input for zip code & country.
  • Lines 24 – 27 are called by actionFunction so the controller knows what radio button was selected.
  • Lines 29 – 37 display the list of results in the pageBlockTable.
  • Lines 39 – 80 is an inner class storing details about a zip code and the methods to expose those details.
  • Lines 92 – 118 calls geonames’ web service.
  • Lines 119 – 165 parses the returned JSON stream and saves the data.

While it feels like overkill to make zipInfo’s member variables private thus requiring all those set methods, i thought it best to maintain a separation of concerns.  This way, if the code get re-used, access is controlled through member functions instead of letting any code manipulate member variables on a whim.

Salesforce ADM201 Certification

I recently passed Salesforce’s Winter 15 ADM201 Administrator certification test!  Although I spend as much time as I can doing development I’m also the solo admin of an org and have a broad range of responsibilities.  So, I decided ADM201 certification was important and having it as an MBO ensured I made the time to do it. 🙂

Salesforce offers test prep classes, but they’re expensive.  Instead, I joined certifiedondemand.com for a very worthwhile $40.  John does a good job organizing the material and his practice test is effective.  I also found a number of other practice test questions online.  One site I like is abetterstudyguide.com.  A few questions show up verbatim on the test, but the scenario-based questions are invaluable because it forces you to think the answer through.  This is helpful as Salesforce changes the form of questions you might find online, but not the topic.

While reading and studying is very necessary there’s no substitute for actual hand’s on practice.  For example, just days before my test date one user asked about why mass edits with enhanced list views didn’t work.  Turns out, they hadn’t set the record type in their filter.  Was there a question on the test about this?  You betcha!

Lastly, I took the online-proctored test due to convenience.  Allot yourself extra time for test setup as the ‘take test’ button is enabled 15 minutes prior to your appointment.  Even though I installed Sentinel well ahead of time, it wasn’t until I tried starting the exam that I realized an external webcam was required!  Since I used the built-in webcam to register I wrongly assumed I could use it to take the test.  Fortunately, the Krypterion support guy was able to reschedule my test 30 minutes later so I could go find an external webcam.  He also helped me setup the webcam as it needs to show your face, hands and keyboard.  So, it worked out in the end, but this is not the kind of stress you want right before your exam!

 

 

Auto-refreshing Case List View With VisualForce

Our case assignment rules assign newly created web-to-case and email-to-cases to a queue which emails our support team.  Unfortunately, a single email isn’t sufficient and we needed a way to quickly see unassigned cases.  It had to auto-refresh, so the data did not grow stale, and use a minimum amount of screen space.  I decided a VisualForce page was best as it could be fully customized, but I had to figure out how to get the data.

I went through three different approaches.  Ultimately, I implemented a custom list controller to populate the data on the VisualForce page and then used the apex:actionPoller component to refresh the page.  Here’s a screenshot of the results:

VisualForce List Controller

VisualForce List Controller

This is the VF page I used:


<apex:page controller="casePollList" >
<div style="width:300px;">
<apex:form >
<apex:pageBlock id="myBlock" >
<apex:actionPoller interval="10" rerender="myBlock" />
<apex:pageBlockTable value="{!cases}" var="c" id="myTable">
<apex:column value="{!c.CaseNumber}"/>
<apex:column value="{!c.Subject}"/>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</div>
</apex:page>

Incidentally the div tag and CSS styling is needed to enforce size restrictions because I couldn’t get the width attribute to work.  That needs to be figured out at another time.

This is the list view controller code:


public class casePollList {
public ApexPages.StandardSetController setCon {
get {
setCon = new ApexPages.StandardSetController(Database.getQueryLocator([SELECT Subject, CaseNumber FROM Case WHERE owner.name='Open Cases']));
return setCon;
}
set;
}
// Initialize setCon and return a list of records
public List<Case> getCases() {
return (List<Case>) setCon.getRecords();
}
}

As I said, I took three different approaches.  My first thought was the Streaming API was ideal. Alas, this didn’t work because PushTopic queries do not support relationships.  Since my SOQL query needed to use the Case Owner’s Name as a filter ( SELECT CaseNumber FROM Case WHERE Owner.Name = ‘Open Cases’) I could not use the Streaming API.

My second approach used apex:enhancedList component.  This component referenced a specific, predefined list view which showed these unassigned cases.  I cribbed Kevin Carothers suggestion to get Javascript to refresh the page.  Here’s what I ended up with:


<apex:page standardController="Case" >
<div style="width:300px;">
<apex:enhancedList height="250" listid="00B600000074y38"/>
</div>
<script type="text/javascript">
isListLoaded();
function isListLoaded(){
setTimeout("location.reload(true);",10000);
}
</script>
</apex:page>

This worked except I wasn’t sure reloading the whole page was a good idea.  I found ways to refresh a part of a page, but I’d have to learn jQuery just to refresh a part of a page.  In the end, I killed this idea because I couldn’t rid of the styling, the buttons and reducing the size of the list view made things ugly.  See this screenshot:

Enhanced List View Ugliness

Enhanced List View Ugliness

 

How To: Check the HTTPS Security Protocol Used

The recent “POODLE” vulnerability announcement articulates a vulnerability in OpenSSL’s SSL protocol 3.0.  As a result, many organizations have disabled the usage of that security protocol.  Salesforce.com’s announcement they would no longer support SSL 3.0 for any in-bound or out-bound traffic left me scrambling to figure out what protocols we used.  Fortunately, I work at a company with a bunch of sharp networking engineers who helped me check the actual wire traffic being sent.  Use tcpdump to capture the packet traffic with the following command line:

tcpdump -i eth0 -s0 -nxX tcp and port 443 -w out.pcap

The manpage for a full description of options, but these are machine dependent:

  • -i = The interface to listen on (use ifconfig to list your interfaces).
  • port 443 = Port number for SSL with 443 being the default.
  • -w out.pcap = Specify the output capture file.

After running tcpdump, run a transaction to access Salseforce and  use Wireshark to open the capture file (out.pcap in my case).  In the screenshot below I highlighted the relevant bits with a red square showing the security protocol used.  As an aside, TLSv1 is not the latest & greatest so it’s time to update my openssl library!

wireshark

Screenshot of Wireshark

How To: Zip code Lookup in a Salesforce Trigger

I showed one example how to look up a zip code using VisualForce and a second more complicated example.  In this post, I’ll show how to lookup a zip code to update the city and state of an account in a trigger.  For simplicity I reused the same basic underlying code and ziptastic.com, but there are some pitfalls to doing this in a trigger:

  • Pitfall #1 – A trigger cannot make a callout due to latency so a new function must be called that’s annotated with “@future.
  • Pitfall #2 – Another annotation (callout = true) must be included to allow callouts
  • Pitfall #3 – SObjects cannot be passed to an @future method.  Instead, IDs must be passed limiting processing to “after insert” triggers instead of “before insert” (because before the record is created the ID is unknown).
  • Pitfall #4 – Because of pitfall #3, we must safeguard we don’t cause an infinite loop.

Here’s my trigger stub:


trigger accountTrig on Account (after insert) {
// When a new account is created, fill in the city and state based on zip code
if (trigger.isAfter && trigger.isInsert) {
AccountClass.onAfterInsert( trigger.new );
}
}

With the above restrictions in mind here’s the Apex class I wrote to do the work.  I put comments in to show what code is dealing with which pitfall listed above.


// Use a name besides "Account" otherwise you'll get compilation problems due to symbol collision
public with sharing class AccountClass {
private static boolean isTrigger = false; // Pitfall #4: Used to prevent infinite loops
public class accountException extends Exception {}
/* format from ziptasticapi.com:
{"country":"US","state":"CA","city":"SAN JOSE"}
*/
// Format returned by ziptastic API
public class ziptasticReturn {
string country;
string state;
string city;
}
// Trigger Handler to do zipCodeLookup when new records are created
// Do this after the record is created so we have an ID we can reference
public static void onAfterInsert( List<Account> triggerNew)
{
// Pitfall #4 – Prevent infinite loops
if (isTrigger == true) {
return; // Just return if this is called as a result of our DML operation
} else
isTrigger = true; // Set this so we know we caused ourselves to be called again
// Must pass IDs & not SObjects to @future because the SObject may change by the time @future executes
List<Id> accountsId = new List<Id>();
for (Account a : triggerNew) {
accountsId.add(a.Id);
}
system.debug(LoggingLevel.Info, 'onAfterInsert called with '+triggerNew+'; sending IDs='+accountsId);
makeZipCallout(accountsId);
}
// Pitfall #1 & #2 – Triggers must do callouts from an '@future' and (callout = true) anotated function
@future (callout = true)
private static void makeZipCallout(List<Id>acct)
{
system.debug(LoggingLevel.Info, 'makeZipCallout with '+acct);
string resp;
// Pitfall #3 – Fetch records of the IDs for updating
List<Account> accountSet = [SELECT Id, BillingPostalCode, BillingCity, BillingState, BillingCountry
FROM account
WHERE Id = :acct];
for (Account a : accountSet) {
// Note this version of the API is only for the US
string endpoint ='http://ziptasticapi.com/&#39;;
endpoint = endpoint + a.BillingPostalCode;
system.debug(LoggingLevel.Info,'zipCode.cls: calling endpoint='+endpoint);
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setMethod('GET');
req.setEndpoint(endpoint);
try {
res = http.send(req);
if (res.getStatusCode() != 200) {
throw new accountException(res.getStatus());
}
} catch (accountException e) {
system.debug(LoggingLevel.Error, 'Error HTTP response code = '+res.getStatusCode()+'; calling '+endpoint );
return;
}
resp = res.getBody();
JSONParser parser = JSON.createParser(resp);
parser.nextToken(); // Start object "{"
ziptasticReturn zipInfo = new ziptasticReturn();
// This convenient method reads the JSON stream into a class in one go
zipInfo = (ziptasticReturn) parser.readValueAs(ziptasticReturn.class);
a.BillingState = zipInfo.state;
a.BillingCity = zipInfo.city;
a.BillingCountry = zipInfo.country;
}
update accountSet; // Call DML operation to update
isTrigger = false; // Pitfall #4 – Trigger is done so reset this
} // makeZipCallout()
}

Without too much work this can also be modified to support a ‘before update’ trigger.  This would always check the account’s city and state matches its zip code anytime an account changes.

How To: Look up zip code for city & state; Visualforce example

This is the first post How To look up zip codes.  A second post demonstrates this in a Salesforce Trigger and this post shows a more sophisticated approach.

Doing a zip code lookup to get city and state is such a common task  and yet the Salesforce platform does not natively support this.  This simple function benefits both the user by making entering addresses easier and the Salesforce administrator by ensuring data cleanliness and uniformity.  Although a number of App-exchange packages support this functionality the ones I browsed are very fully featured and way overkill for a simple zip code lookup.  So, I went looking for a free zip code lookup service I could use.  There are a number of sources – even the US Postal Service does this!  I used ziptasticapi.com because it’s very straightforward to implement as we shall see.

I created a VisualForce page for the UI and a matching Apex custom controller to implement the REST callout and supporting logic.  Below is a screenshot of the VF page.  Users enter their 5 digit zip code and click Lookup!  The bottom half then displays the city, state and country.

Visualforce page for zip code lookup

Here’s the VF source code.  A bit of explanation for orientation:  The first <apex:pageBlock> component accepts the user input and includes the Lookup! button.  The second <apex:pageBlock> component refreshes with the city, state and country when the Lookup button is pushed.  For more details and examples of VF development, I recommend checking out the VisualForce Workbook.  Tutorial #11 describes the refresh technique shown here.  For completeness, here’s the VisualForce Developer’s Guide as well.


<apex:page controller="zipCode">
<apex:form >
<!– This block takes the user input and refreshes the below pageBlock when the Lookup! button is pressed –>
<apex:pageBlock >
<apex:actionRegion >
<apex:outputLabel value="Enter 5 digit zip code " for="inputzip" />
<apex:inputText value="{!zipValue}" id="inputzip" />
<!– The rerender attribute causes addressList to be re-rendered when the button is clicked –>
<apex:commandButton action="{!lookup}" value="Lookup!" rerender="addressList">
</apex:commandButton>
</apex:actionRegion>
</apex:pageBlock>
<!– This section displays the results –>
<apex:pageBlock >
<apex:outputPanel id="addressList">
<apex:pageBlockSection columns="3" title="Zipcode lookup">
<apex:outputText label="city" value="{!cityAddr}" />
<apex:outputText label="state" value="{!stateAddr}" />
<apex:outputText label="country" value="{!countryAddr}" />
</apex:pageBlockSection>
</apex:outputPanel>
</apex:pageBlock>
</apex:form>
</apex:page>

view raw

zipcodeExample

hosted with ❤ by GitHub

Below is the matching controller.  The top half of the class contains the property declarations the VF page binds to for I/O.  The lookup() method in the bottom half is where all the good stuff happens.   After building the end point, making the callout and basic error checking, I invoke Salesforce’s JSONparser class.  This handy class contains functions to aid in parsing of JSON responses.  Here’s Salesforce’s example of using the class.  One feature not used in the example is the readValueAs() method.  As I looked for examples of JSON parsing (because let’s face it who wants to write Yet Another Parser from scratch?) I came across Abhinav Gupta’s blog post about parsing JSON in one line of code.  All that’s needed is to declare a class matching the JSON response, advance the stream pointer to desired data and call readValueAs().  In one fell swoop the response is slurped up into your class for easy digestion.  For more details take a gander at the Apex Developer’s Guide.


public with sharing class zipCode {
// Constructor
public zipCode() {
zipValue = 0;
cityAddr = 'None';
stateAddr = 'None';
countryAddr = 'None';
}
/* format from ziptasticapi.com:
{"country":"US","state":"CA","city":"SAN JOSE"}
*/
// Format returned by ziptastic API
public class ziptasticReturn {
string country;
string state;
string city;
}
public class zipCodeException extends Exception {}
// zipValue property is used by VF page and is the zipcode entered by user
public integer zipValue {
get { return zipValue; }
set { zipValue = value; }
}
// stateAddr is a property VF page calls to get the state corresponding to zipValue
public string stateAddr {
get { return stateAddr; }
private set { stateAddr = value; }
}
// cityAddr is a property VF page calls to get the city corresponding to zipValue
public string cityAddr {
get { return cityAddr; }
private set { cityAddr = value; }
}
// countryAddr is a property VF page calls to get the country corresponding to zipValue
public string countryAddr {
get { return countryAddr; }
private set { countryAddr = value; }
}
// Called by commandbutton on VF page to look up the zip code via REST API
// Returns null so VF page won't navigate to a new page
public PageReference lookup()
{
string resp;
ziptasticReturn zipInfo;
// Note this version of the API is only for the US
string endpoint ='http://ziptasticapi.com/&#39;;
endpoint = endpoint + zipValue;
system.debug(LoggingLevel.Error,'zipCode.cls: calling endpoint='+endpoint);
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setMethod('GET');
req.setEndpoint(endpoint);
try {
res = http.send(req);
if (res.getStatusCode() != 200) {
throw new zipCodeException(res.getStatus());
}
} catch (zipCodeException e) {
system.debug(LoggingLevel.Error, 'Error HTTP response code = '+res.getStatusCode()+'; calling '+endpoint );
return null;
}
resp = res.getBody();
JSONParser parser = JSON.createParser(resp);
parser.nextToken(); // Start object "{"
// This convenient method reads the JSON stream into a class in one go
zipInfo = (ziptasticReturn) parser.readValueAs(ziptasticReturn.class);
stateAddr = zipInfo.state;
cityAddr = zipInfo.city;
countryAddr = zipInfo.country;
return null;
}
}

view raw

zipCode.cls

hosted with ❤ by GitHub

One last detail:  If you want to try the above code, you’ll need to add permissions for Salesforce to make the callout to ziptastic.  You can do this by going to Setup | Security Controls | Remote Site Settings and adding a new Remote site to the list.

Changing Your Apex Class’s API Version

I recently ran into a problem trying to install a managed package in my org.  Salesforce gave a “This will take a while so I’ll email you when it’s done” message.  Although I’d get the email, package would fail to install.  I discovered the failure was because my unit tests (UTs) failed during the package installation.  Oddly, the UTs passed when run manually.  By using the debug logs, I discovered the the failure managed package because it used API v31 whereas my UT class used v24.  By changing my UT to use API v31, it failed every time.  Debugging & fixing the problem then became the standard exercise of examining debug logs to ascertain what went wrong.

To change your class’s API version use Setup | Develop | Apex Classes  and hit Edit next to your class (see below).

Edit Apex class attributes

Edit Apex class attributes

 

Next, click the Version settings tab and use the picker to choose the desired Salesforce.com API as per below.

Edit API Version

Changing API versions from the SFDC UI

Note – Changing API versions like this can only be done in a sandbox or developer org.  You cannot directly change production like this, though.

 

You can also use Developer Console to change the API version.  Simply File | Open | <class name> and there’s an API version drop down available to you.  Developer Console still won’t let you directly change production so you’ll have to deploy the change from sandbox into production to update the metadata.

Using Developer Console to edit API version

Using Developer Console to change API versions

 

 

Creating Dynamic Choices In Cloud Flow

I started using Salesforce’s Cloud Flow (aka Visual Flow) to implement some business logic and was looking to implement the equivalent of a user selectable lookup option in a flow.  I ran across this question about handling multiple records and Elcalvo Mike suggested a dynamic choice.  BigWill asked how this could be used to do a partial match on account names.  So, I created a simple flow to demonstrate this.  

The screenshot in the below left shows a user entered the string “test” into a Screen Input Field that I named “Account_to_search_for”.  The screenshot in the below right is the dynamic choice drop down listing accounts with “test” in their name.  The user clicks a selection to save their choice.

Dynamic Choice example first screenshot      Dynamic Choice example second screenshot

 

This next screenshot illustrates creating the dynamic choice.  It performs a record lookup on Accounts whose Name is a partial match for the user input (the blue highlighted “Account_to_search_for” is the Screen Input Field from the above left screenshot).  The data listed in the drop down are all the returned records’ Names (specified in the Choice Selected Value).  

Dynamic Choice Screenshot

Dynamic Choice Screenshot

One last feature is under the “Additional Options” which lets you specify what fields get saved for the user selected record.  For my example, I saved the account name and Salesforce ID for display.

Screen Shot 2014-07-09 at 11.58.00 PM

Hope this helped!