Sunday 28 August 2016

Stand and Deliver with Salesforce Geocoding

Stand and Deliver with Salesforce Geocoding

Introduction

The Summer 16 release of Salesforce introduced Automatic Geocodes for Addresses which, as the name suggests, allows you to geocode records with addresses automatically. To enable this just go to Setup, type ‘Clean' into the search and click on Clean Rules from the resulting setup nodes.  Then activate one or more of the clean rules and wait a few minutes. Much easier than setting up a batch job to integrate with Google and then finding yourself in breach of the license terms! 

In conjunction with the Geolocation API, this new feature allows you to build some cool applications without too much effort. In this post I’ll show how to combine these two technologies to build an application that assigns a delivery to the nearest user (driver, bike messenger etc) to the collection location.

User Check In

The functionality to allow users to check in (record their location) is provided by a Lightning Component. The component includes the Lightning Design System (yeah, I know it's part of the platform now, but including it myself means that I can also use this component in lightning out) and after the CSS has loaded, uses the geolocation API to capture the current location:

    doInit : function(cmp, ev) {
        var self=this;
        if (navigator.geolocation) {
            navigator.geolocation.getCurrentPosition(
                function(result) {self.gotGeoCode(result, cmp, self);},
                self.error,
                {
                maximumAge: 0,
                timeout:30000,
                enableHighAccuracy: true
                }
            );
        }
    },

The parameters passed to the geolocation function are as follows:

  • maximumAge - the maximum age of a cached position. Setting this to zero ensures that a new position is captured
  • timeout - the maximum number of milliseconds to wait for a response
  • enableHighAccuracy - requests that the most accurate position is captured. A request specifying this typically takes longer and consumes more power, and may be denied by the hardware. It’s just a suggestion really.

Once captured, the a server side Apex action is invoked to store the position in a custom field on the user record, along with a timestamp of when it was captured:

    @AuraEnabled
    public static String StoreUserLocation(Decimal latitude, Decimal longitude)
    {
        String result='SUCCESS';
        try
        {
	    User u=new User(id=UserInfo.getUserId(),
    	                    Location__Latitude__s=latitude,
                           Location__Longitude__s=longitude,
            	            Location_Timestamp__c=System.now());
	    update u;
        }
        catch (Exception e)
        {
            result=e.getMessage();
        }
        
        return result;
    }

Note that I don’t need to retrieve the user record from the database to update it, I can just instantiate a new User object in memory and specify the id based on the UserInfo global - always worth saving a SOQL call if possible.

The component in action is shown in the screen capture video below:

Delivery Request

The flip side of the user check in is the delivery request. This is where a customer requires something to be collected from one (their) account and delivered to another. As this isn’t something that is likely to be requested on the fly, this is handled by a Visualforce page, although still using the Lightning Design System to style a Visualforce form.

To create a new delivery, the customer (me in this case) specifies the From and To account and any specific delivery instructions. I’m sending a package to Universal Containers as they seem to have a new social media team and I am keen to engage with them:

Screen Shot 2016 08 28 at 17 55 11

The package is being sent from Sea Palling Beach, where myself and the Jobs Admin have recently checked in:

Screen Shot 2016 08 28 at 17 56 44

When the record is saved, the controller sets the owner to the user who has most recently checked in to a location within 50 miles - if no such user can be found, the owner will be left as the user that created the record:

 

    public PageReference save()
    {
        Account acc=[select ShippingLatitude, ShippingLongitude
                     from Account
                     where id=:delivery.From__c];
	List users=[SELECT id, Location__c, Location_Timestamp__c
	 	    FROM User
	 	    WHERE DISTANCE(Location__c, GEOLOCATION(:acc.ShippingLatitude, :acc.ShippingLongitude), 'mi') < 50
                    ORDER BY Location_Timestamp__c DESC];
	
	if (users.size()>0)
        {
            delivery.OwnerId=users[0].id;
        }
        
        insert delivery;
        ApexPages.StandardController std=new ApexPages.standardController(delivery);
        return std.view();
    }

 So even though I created the delivery request, it was assigned to the job admin user as they checked in more recently:

Screen Shot 2016 08 28 at 18 18 31

That’s It?

That is indeed  all there is to it - a couple of custom fields on the user object, a custom object, one lighting component, one Visualforce page and two Apex classes. If you are so inclined, you can view the full code at the github repository.