Salesforce Field Service Integration with Route Optimization API

Prerequisites

To successfully integrate Salesforce Field Service with NextBillion.ai, ensure you have the following prerequisites in place:

  1. Salesforce Field Service Account Credentials: You will need valid credentials, including a username and password, to access your Salesforce Field Service account.

  2. NextBillion.ai's API Key: Obtain an API key from NextBillion.ai, which serves as the authentication mechanism for accessing their services and data. This key is essential for secure and authorized communication between your systems and NextBillion.ai's APIs.

Generating Access Token

  • Create a Salesforce Account: Begin by setting up a Salesforce account if you haven't already.

  • Create a Custom App: Once your Salesforce account is established, create a custom application within the Salesforce platform.


    docs-image
  • Configure OAuth for Connection: Configure OAuth settings to establish a secure connection. Before testing the connection, keep these important considerations in mind:

    • Specify Callback URL: Ensure that you specify a valid callback URL.

    • Retrieve Consumer Key and Secret: Obtain your Consumer Key and Secret, which are essential for authentication.

    docs-image
  • Test the API Connection: Use Postman or a similar tool to test the API connection and generate the access token.


    docs-image
  • Generate API Access Token: After testing the connection successfully, generate the API access token.

  • Verify Basic CRUD Operations: Finally, verify that basic CRUD (Create, Read, Update, Delete) operations are functioning as expected using the generated access token.

Populate Data from Salesforce

To enrich your Salesforce application, you need to populate the following key components:

Service Appointments

Import and integrate service appointment data from your Salesforce application.

docs-image

Sample API Request

When the following cURL command is executed, it sends a GET request to Salesforce, which responds with the results of the specified query, typically in JSON format. This allows you to retrieve Salesforce data that matches the criteria defined in the query, which can then be used for integration purposes.

1
2
3
# Fetch addresses from Salesforce
curl --request GET \
    --url https://nextbillionai-dev-ed.my.salesforce.com/services/data/v58.0/query?q=SELECT+AccountId,Address,AppointmentNumber,Id__c +FROM+ServiceAppointment

Sample API Response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
{
  "totalSize": 5,
  "done": true,
  "records": [
    {
      "attributes": {
        "type": "ServiceAppointment",
        "url": "/services/data/v58.0/sobjects/ServiceAppointment/08p8c000001LFxdAAG"
      },
      "AccountId": "0018c00002okLOJAA2",
      "Address": {
        "city": "Chennai",
        "country": "India",
        "geocodeAccuracy": "Zip",
        "latitude": 13.03552,
        "longitude": 80.22181,
        "postalCode": "600033",
        "state": "Tamilnadu",
        "street": "Appasamy Appartments"
      },
      "AppointmentNumber": "SA-0001",
      "Id__c": 1
    },
    {
      "attributes": {
        "type": "ServiceAppointment",
        "url": "/services/data/v58.0/sobjects/ServiceAppointment/08p8c000001LIefAAG"
      },
      "AccountId": "0018c00002okLOJAA2",
  "Address": {
        "city": "Chennai",
        "country": "India",
        "geocodeAccuracy": "Zip",
        "latitude": 12.92428,
        "longitude": 80.25026,
        "postalCode": "600115",
        "state": "Tamilnadu",
        "street": "Dr. Shalini Old Age Home"
      },
      "AppointmentNumber": "SA-0002",
      "Id__c": 2
    },
    {
      "attributes": {
        "type": "ServiceAppointment",
        "url": "/services/data/v58.0/sobjects/ServiceAppointment/08p8c000001LJOlAAO"
      },
      "AccountId": "0018c00002okLOJAA2",
      "Address": {
        "city": "Chennai",
        "country": "India",
        "geocodeAccuracy": "Zip",
        "latitude": 12.79778,
        "longitude": 80.21113,
        "postalCode": "603103",
        "state": "Tamilnadu",
        "street": "Isha Code Field Villa"
      },
      "AppointmentNumber": "SA-0003",
      "Id__c": 3
    },
    {
      "attributes": {
        "type": "ServiceAppointment",
        "url": "/services/data/v58.0/sobjects/ServiceAppointment/08p8c000001LKgHAAW"
      },
      "AccountId": "0018c00002okLOJAA2",
      "Address": {
        "city": "Chennai",
        "country": "India",
        "geocodeAccuracy": "Zip",
        "latitude": 12.94937,
        "longitude": 80.14924,
        "postalCode": "600044",
        "state": "Tamilnadu",
        "street": "Priyanka Appartments"
      },
   "AppointmentNumber": "SA-0006",
      "Id__c": 6
    },
    {
      "attributes": {
        "type": "ServiceAppointment",
        "url": "/services/data/v58.0/sobjects/ServiceAppointment/08p8c000001LJx7AAG"
      },
      "AccountId": "0018c00002okLOJAA2",
      "Address": {
        "city": "Chennai",
        "country": "India",
        "geocodeAccuracy": "Zip",
        "latitude": 13.01267,
        "longitude": 80.20937,
        "postalCode": "600032",
        "state": "Tamilnadu",
        "street": "VGN Fairmount"
      },
      "AppointmentNumber": "SA-0005",
      "Id__c": 5
    }
  ]
}

The integrated dashboard will resemble the following image:

docs-image

Service Resources

Likewise, ensure that service resource information is efficiently integrated and populated within your Salesforce environment. Retrieve a list of your Technicians, which represent your vehicles. This information will be used to optimize routes based on vehicle capacity and other parameters.

docs-image

Sample API Request

1
2
curl --request GET \
    --url https://nextbillionai-dev-ed.my.salesforce.com//services/data/v58.0/query?q=SELECT+id__c,Name+FROM+ServiceResource
docs-image

Sample API Response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "totalSize": 2,
  "done": true,
  "records": [
    {
      "attributes": {
        "type": "ServiceResource",
        "url": "/services/data/v58.0/sobjects/ServiceResource/0Hn8c000000ELyyCAG"
      },
      "id__c": 2,
      "Name": "Technician 2"
    },
    {
      "attributes": {
        "type": "ServiceResource",
        "url": "/services/data/v58.0/sobjects/ServiceResource/0Hn8c000000ELyjCAG"
      },
      "id__c": 1,
      "Name": "Technician 1"
    }
  ]
}

After successfully populating the data from Salesforce, your integerated dashboard will look something like this:

docs-image

Integration with Route Optimization API

After successfully pulling the necessary data from Salesforce, integrate the fetched data with Route Optimization using the following API endpoint.

Map the fetched Salesforce data to the respective API parameters. Configure optimization settings such as shift start and end time, service time, and start and end location coordinates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
let vehicles = [];
let shifts = [];
let starts = [];
let ends = [];
let veh_attr = [];
let stop_attr = [];
let shiftStart = (new Date(dtShiftStart2.value).getTime()/1000);
let shiftEnd = (new Date(dtShiftEnd2.value).getTime()/1000);


// START NBAI PROBLEM BUILD
let locations = [];
let jobs = [];
let shipments = [];
vehicles = [];
veh_attr = [];
stop_attr = [];
// sequence through all stops and create job array while
// also creating the indexed list of locations 
let location_index = 0;
let location = [];
tblStops.data.forEach(planstop => {

      let job = {
        id: parseInt(planstop.Id__c),
        description: planstop.Address.street,
        location_index: location_index++,
        service: numberInput1.value,
        priority: 0,
        time_windows: [[shiftStart,shiftEnd]]
      }
      location.push(`${planstop.Address.latitude},${planstop.Address.longitude}`);

      jobs.push(job);
  
});

// Add shift start/stop location to location index
location.push(`${txtVehicleStartPosition2.value}`);
location.push(`${txtVehicleEndPosition2.value}`);
location_index += 1;
let veh_fixed_cost = 0;
tblDrivers.data.forEach( (v,idx) => {
  let breaks = [];
  let vehicle = {
    id: parseInt(v.id__c),
    start_index: location_index,
    time_window: [shiftStart, shiftEnd],
    //skills: v.skills,
   // breaks: breaks,
    max_tasks: 25,
    costs: {
      fixed: veh_fixed_cost
    }
  };
    vehicles.push(vehicle);
    veh_fixed_cost += 0;
  
  //}
});

locations = {
  id: 12,
  description: 'Salesforce Test',
  location: location
};

let options = {
  routing: {
    mode: "car"
  }
};


localStorage.setValue('vehiclesNBAI', vehicles);
localStorage.setValue('shipmentsNBAI', shipments);
localStorage.setValue('jobsNBAI', jobs);
localStorage.setValue('locationsNBAI', locations);
localStorage.setValue('nbaiOptions', options);
localStorage.setValue('nbaiVRPResult', null);

nbaiOptimizationRun.trigger();

Once you have successfully retrieved the required data from Salesforce, the next step is to seamlessly integrate this data with the Route Optimization service using the provided API.

Follow these steps to achieve a successful integration:

Step 1: Define Data Structures

Begin by setting up the necessary data structures to facilitate the integration. These include arrays and variables for vehicles, shifts, start and end locations, vehicle attributes, and stop attributes.

1
2
3
4
5
6
let vehicles = [];
let shifts = [];
let starts = [];
let ends = [];
let veh_attr = [];
let stop_attr = [];

Step 2: Configure Optimization Settings

Specify optimization settings to tailor the routing process to your specific needs. These settings typically include shift start and end times, service times, and the coordinates of start and end locations.

1
2
let shiftStart = (new Date(dtShiftStart2.value).getTime() / 1000);
let shiftEnd = (new Date(dtShiftEnd2.value).getTime() / 1000);

Step 3: Prepare Data for Route Optimization

Assemble the data required for route optimization. This involves creating arrays for locations, jobs, and shipments. Additionally, you'll populate the vehicle and job attributes as needed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
let locations = [];
let jobs = [];
let shipments = [];
vehicles = [];
veh_attr = [];
stop_attr = [];

// Iterate through stops to create job array and index locations
let location_index = 0;
let location = [];
tblStops.data.forEach(planstop => {
  let job = {
    id: parseInt(planstop.Id__c),
    description: planstop.Address.street,
    location_index: location_index++,
    service: numberInput1.value,
    priority: 0,
    time_windows: [[shiftStart, shiftEnd]]
  };
  location.push(`${planstop.Address.latitude},${planstop.Address.longitude}`);
  jobs.push(job);
});

// Add shift start/stop locations to the location index
location.push(`${txtVehicleStartPosition2.value}`);
location.push(`${txtVehicleEndPosition2.value}`);
location_index += 1;

// Define vehicle fixed cost and configure vehicles
let veh_fixed_cost = 0;
tblDrivers.data.forEach((v, idx) => {
  let breaks = [];
  let vehicle = {
    id: parseInt(v.id__c),
    start_index: location_index,
    time_window: [shiftStart, shiftEnd],
    max_tasks: 25,
    costs: {
      fixed: veh_fixed_cost
    }
  };
  vehicles.push(vehicle);
  veh_fixed_cost += 0; // Adjust fixed cost as needed
});

// Create the 'locations' object and 'options' for routing
locations = {
  id: 12,
  description: 'Salesforce Test',
  location: location
};

let options = {
  routing: {
    mode: "car"
  }
};

Step 4: Store Data and Trigger Optimization

Store the prepared data structures in local storage for future reference and initiate the route optimization process.

1
2
3
4
5
6
7
8
localStorage.setValue('vehiclesNBAI', vehicles);
localStorage.setValue('shipmentsNBAI', shipments);
localStorage.setValue('jobsNBAI', jobs);
localStorage.setValue('locationsNBAI', locations);
localStorage.setValue('nbaiOptions', options);
localStorage.setValue('nbaiVRPResult', null);

nbaiOptimizationRun.trigger();

By following these steps, you can effectively connect and integrate Salesforce data with the Route Optimization API, ensuring efficient routing and resource allocation for your tasks or deliveries.

Create Jobs with Route Optimization API

This integration step showcases how to utilize NextBillion.ai's Route Optimization API to create optimization jobs, allowing for efficient route planning and resource allocation. The API response provides critical job information, enabling further interaction and tracking within the integration workflow.

POST Query

To create optimization jobs, a POST request is sent to the designated API endpoint using the curl command-line tool or an equivalent HTTP client.

1
2
curl --request POST \
    --url https://api.nextbillion.io/optimization/v2?key={{localStorage.values.apiKey}}

API Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
{
  "request": {
    "url": "https://api.nextbillion.io/optimization/v2?key=<your api key>",
    "method": "POST",
    "body": "{\"locations\":{\"id\":12,\"description\":\"Salesforce Test\",\"location\":[\"13.03552,80.22181\",\"12.92428,80.25026\",\"12.79778,80.21113\",\"12.94937,80.14924\",\"13.01267,80.20937\",\"13.03552,80.22181\",\"13.03552,80.22181\"]},\"jobs\":[{\"id\":1,\"description\":\"Appasamy Appartments\",\"location_index\":0,\"service\":120,\"priority\":0,\"time_windows\":[[1694658600,1694676600]]},{\"id\":2,\"description\":\"Dr. Shalini Old Age Home\",\"location_index\":1,\"service\":120,\"priority\":0,\"time_windows\":[[1694658600,1694676600]]},{\"id\":3,\"description\":\"Isha Code Field Villa\",\"location_index\":2,\"service\":120,\"priority\":0,\"time_windows\":[[1694658600,1694676600]]},{\"id\":6,\"description\":\"Priyanka Appartments\",\"location_index\":3,\"service\":120,\"priority\":0,\"time_windows\":[[1694658600,1694676600]]},{\"id\":5,\"description\":\"VGN Fairmount\",\"location_index\":4,\"service\":120,\"priority\":0,\"time_windows\":[[1694658600,1694676600]]}],\"vehicles\":[{\"id\":2,\"start_index\":6,\"time_window\":[1694658600,1694676600],\"max_tasks\":25,\"costs\":{\"fixed\":0}},{\"id\":1,\"start_index\":6,\"time_window\":[1694658600,1694676600],\"max_tasks\":25,\"costs\":{\"fixed\":0}}],\"options\":{\"routing\":{\"mode\":\"car\"}},\"result\":null}",
    "headers": {
      "Content-Type": "application/json",
      "User-Agent": "Retool/2.0 (+https://docs.tryretool.com/docs/apis)",
      "ot-baggage-requestId": "undefined",
      "x-datadog-trace-id": "3635335836028342090",
      "x-datadog-parent-id": "3092369415317155632",
      "x-datadog-sampling-priority": "-1",
      "traceparent": "00-000000000000000032734eae99b78f4a-2aea4d996242ef30-00",
      "tracestate": "dd=s:-1",
      "X-Retool-Forwarded-For": "183.82.29.95"
    }
  },
  "response": {
    "data": {
      "id": "cf7a86c2fd8d6c1b9e87eb87c5af154b",
      "message": "Optimization job created",
      "status": "Ok",
      "warning": [
        "location_index[5] is unused"
      ]
    },
    "headers": {
      "server": [
        "nginx/1.25.2"
      ],
      "date": [
        "Wed, 13 Sep 2023 09:08:20 GMT"
      ],
      "content-type": [
        "application/json; charset=utf-8"
      ],
      "content-length": [
        "134"
      ],
      "access-control-allow-origin": [
        "*"
      ],
      "access-control-allow-headers": [
        "authorization"
      ],
      "access-control-allow-methods": [
        "DELETE,GET,HEAD,PUT,PATCH,POST,OPTIONS"
      ],
      "strict-transport-security": [
        "max-age=15724800; includeSubDomains"
      ],
      "via": [
        "1.1 google"
      ],
      "alt-svc": [
        "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000"
      ]
    },
    "status": 200,
    "statusText": "OK"
  }
}

The request payload contains essential information needed for the optimization job. It is structured as a JSON object and includes the following elements:

  • locations: Specifies location data, including IDs, descriptions, coordinates, and time windows.

  • jobs: Defines job details, such as IDs, descriptions, location indices, service times, and time windows.

  • vehicles: Describes vehicle properties, including IDs, starting indices, time windows, and task capacities.

  • options: Specifies optimization settings, such as routing mode.

  • result: Initially set to null, as it awaits the response from the API.

API Response

Upon successfully processing the request, the API responds with a JSON object containing the following details:

1
2
3
4
5
{
  "id": "456fd9f88e72cf92aec1bd68cfa81cea",
  "message": "Optimization job created",
  "status": "Ok"
}

Retrieving Optimized Route Result

To obtain the optimized route results after initiating a route optimization job, you can utilize the provided GET query. This query enables you to fetch the optimized route details from the Route Optimization API.

GET Query

    https://api.nextbillion.io/optimization/v2/result?id={{nbaiOptimizationRun.data.id}}&key={{localStorage.values.apiKey}}

In the above URL, two query parameters are used:

  • id: This parameter specifies the unique identifier associated with the optimization job. It is obtained from the nbaiOptimizationRun.data.id variable.

  • key: The localStorage.values.apiKey variable provides the API key necessary for authentication.

Example JSON Response

Upon making the GET request, the API will respond with a JSON object containing the optimized route details. Here's an example of what the JSON response may look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
{
  "result": {
    "code": 0,
    "summary": {
      "cost": 6385,
      "routes": 2,
      "unassigned": 0,
      "setup": 0,
      "service": 600,
      "duration": 6385,
      "waiting_time": 0,
      "priority": 0,
      "distance": 52046.399999999994
    },
    "routes": [
      {
        "vehicle": 2,
        "cost": 0,
        "steps": [
          {
            "type": "start",
            "arrival": 1694658600,
            "duration": 0,
            "service": 0,
            "waiting_time": 0,
            "location": [
              13.03552,
              80.22181
            ],
            "location_index": 6
          },
          {
            "type": "job",
            "arrival": 1694658600,
            "duration": 0,
            "service": 120,
            "waiting_time": 0,
            "location": [
              13.03552,
              80.22181
            ],
            "location_index": 0,
            "id": 1,
            "description": "Appasamy Appartments"
          },
          {
            "type": "end",
            "arrival": 1694658720,
            "duration": 0,
            "service": 0,
            "waiting_time": 0,
            "location": [
              13.03552,
              80.22181
            ],
            "location_index": 0
          }
        ],
        "service": 120,
        "duration": 0,
        "waiting_time": 0,
        "priority": 0,
        "distance": 0,
        "geometry": "o`qnAwhshN????"
      },
      {
        "vehicle": 1,
        "cost": 6385,
        "steps": [
          {
            "type": "start",
            "arrival": 1694658600,
            "duration": 0,
            "service": 0,
            "waiting_time": 0,
            "location": [
              13.03552,
              80.22181
            ],
            "location_index": 6
          },
          {
            "type": "job",
            "arrival": 1694659264,
            "duration": 664,
            "service": 120,
            "waiting_time": 0,
            "location": [
              13.01267,
              80.20937
            ],
            "location_index": 4,
            "id": 5,
            "description": "VGN Fairmount"
          },
          {
            "type": "job",
            "arrival": 1694660873,
            "duration": 2153,
            "service": 120,
            "waiting_time": 0,
            "location": [
              12.94937,
              80.14924
            ],
            "location_index": 3,
            "id": 6,
            "description": "Priyanka Appartments"
          },
          {
            "type": "job",
            "arrival": 1694663107,
            "duration": 4267,
            "service": 120,
            "waiting_time": 0,
            "location": [
              12.92428,
              80.25026
            ],
            "location_index": 1,
            "id": 2,
            "description": "Dr. Shalini Old Age Home"
          },
          {
            "type": "job",
            "arrival": 1694665345,
            "duration": 6385,
            "service": 120,
            "waiting_time": 0,
            "location": [
              12.79778,
              80.21113
            ],
            "location_index": 2,
            "id": 3,
            "description": "Isha Code Field Villa"
          },
          {
            "type": "end",
            "arrival": 1694665465,
            "duration": 6385,
            "service": 0,
            "waiting_time": 0,
            "location": [
              12.79778,
              80.21113
            ],
            "location_index": 2
          }
        ],
        "service": 480,
        "duration": 6385,
        "waiting_time": 0,
        "priority": 0,
        "distance": 52046.399999999994,
        "geometry": "o`qnAwhshNh@rBN`@DZDEFAFABAN?D?L?@?D?@?@?D@X@B@P@D@F@B?F@b@DB?...LBHHJNBJBTDpAXl@Lb@JB?Mx@i@~B??"
      }
    ]
  },
  "status": "Ok",
  "message": ""
}

Accessing Optimized Routes in NextBillion.ai’s Optimizer

To visualize and analyze the optimized routes generated by NextBillion's Optimizer, you can use the following URL.

https://playground.nextbillion.ai/optimization-tester?apiKey={{localStorage.values.apiKey}}&requestID={{nbaiOptimizationRun.data.id}}

The above URL will take you to NextBillion's Optimizer interface where you can explore and interact with the optimized route results associated with your specific request ID and API key. This provides a convenient way to gain insights into your optimized routes for enhanced decision-making and resource management

docs-image

By integrating Salesforce Field Service with NextBillion.ai's Route Optimization API, businesses can streamline field operations, reduce costs, and provide better service to customers. This integration enhances efficiency and competitiveness in the market, ultimately leading to improved customer satisfaction.