Posts

From Pratyush Kumar : Orchestrate Appointment Experience based on Language selected

Introduction :

This blog suggests a way that allows appointment booking with service resources who have support for a set of languages. Thus, we will be adding language support to our existing Appointment Booking flows.

Problem Statement:

Let’s say we have a Service Resource who can speak only a certain set of languages, say Hindi and English and another who can speak in only Chinese. Even though each of the resources have all the relevant skills for attending a Work Type, he/she will not be able to drive the appointment since they cannot speak a certain language.

By introducing language support we will try to eliminate this problem.

Solution Approach:

  1. We manage a multi-select pick-list custom field on the Service Resource Object which will store the list of languages the resources can support
  2. Add a selection to the flow that will display the languages – so that requestor can opt for languages he/she prefers to get the service in
  3. After selection of languages in flow, the FilterByResourceIds is populated by intersection of ( getAppointmentCandidates API call and SOSL to fetch resources supporting the list of languages)
  4. List the available service resources by filtering out those who know a specific language by leveraging “filterByResources”

Setup Steps:

Create a Global Value Set

Create a new Global Value Set , with a defined set of language values.

Screenshot 2022-11-16 at 9.09.20 AM.png

Add a Multi-Select Picklist Field on Service Resource

Add a multi-select picklist field on Service Resource which takes values from the earlier defined global value set.

Screenshot 2022-11-16 at 9.12.19 AM.png

Create an Apex Class

Add following code to create an Apex Class ( Note that this class method will be used to set the FilterByResourceIds parameter):

There are two ways in which we can create this action.:

  1. When there are many STM records for the selected ServiceTerritory, an extra API call can be used to reduce the scope of our query.

public class MultiLanguage {

   @InvocableMethod(label=’Get Service Resource Ids’)

   public static List<String> getServiceResourceIds(List<Input> inputs){

       lxscheduler.GetAppointmentCandidatesInput input = new lxscheduler.GetAppointmentCandidatesInputBuilder()

               .setWorkTypeGroupId(inputs.get(0).workTypeGroupId)

               .setTerritoryIds(new List<String>{

                       inputs.get(0).serviceTerritoryId

               })

               .setStartTime(System.now().format(‘yyyy-MM-dd\’T\’HH:mm:ssZ’))

               .setEndTime(System.now().addDays(14).format(‘yyyy-MM-dd\’T\’HH:mm:ssZ’))

               .setAccountId(inputs.get(0).accountId)

               .build();

       String vString = lxscheduler.SchedulerResources.getAppointmentCandidates(input);

       List<Object> list1 = (List<Object>)Json.deserializeUntyped(vString);

       Set<String> test1 = new Set<String>();

       for(Object obj:list1){

           Map<String,Object> map1 = (Map<String,Object>)obj;

           List<Object> test2 = (List<Object>)map1.get(‘resources’);

           for(Object s:test2){

                test1.add((String)s);  

           }           

       }

       System.debug(‘[RETURNED_RESULT]’ + test1);

       String languages = inputs.get(0).languagesSelected;

       List<String>languagesList = languages.split(‘;’);

       languages = ‘(\” + String.join(languagesList, ‘\’,\”) + ‘\’)’;

       List<ServiceResource> resources = DataBase.query(‘select id from ServiceResource where Languages__c includes ‘ + languages + ‘and id in :test1 and IsActive = true’);

       List<String> list2 = new List<String>();

       for(ServiceResource r:resources){

           list2.add(r.id);

       }

       return new List<String>{String.join(list2, ‘,’)};

   }

   public class Input{

       @InvocableVariable

       public String serviceTerritoryId;

       @InvocableVariable

       public String workTypeGroupId;

       @InvocableVariable

       public String accountId;

       @InvocableVariable

       public String languagesSelected;

   }

}

  1. In cases when the count of STM is not that large we can directly query without an extra API call.

public class MultiLanguage2 {

   @InvocableMethod(label=’Get Service Resource Ids2′)

   public static List<String> getServiceResourceIds(List<Input> inputs){

       String languages = inputs.get(0).languagesSelected;

       List<String>languagesList = languages.split(‘;’);

       languages = ‘(\” + String.join(languagesList, ‘\’,\”) + ‘\’)’;

       String serviceTerritoryId = inputs.get(0).serviceTerritoryId;

       List<ServiceTerritoryMember> resources = DataBase.query(‘select ServiceResource.Id from ServiceTerritoryMember where ServiceResource.Languages__c includes’ + languages + ‘and ServiceResource.IsActive = true and ServiceTerritory.Id =:serviceTerritoryId’);    

       List<String> list2 = new List<String>();

       for(ServiceTerritoryMember r:resources){

           list2.add(r.ServiceResource.Id);

       }

       return new List<String>{String.join(list2, ‘,’)};

   }

   public class Input{

       @InvocableVariable

       public String serviceTerritoryId;

       @InvocableVariable

       public String workTypeGroupId;

       @InvocableVariable

       public String accountId;

       @InvocableVariable

       public String languagesSelected;

   }

}

Flow:

  1. Clone the existing Outbound New Appointment Flow.
  2. Add following Elements:
    • Variable : selectedLanguages of type Text.
    • Screen : Language Screen ( This screen will show the picklist values of Global Value Set to choose from set of languages )
      • Add a Multi-Select Picklist Component to the Screen
      • Under the Choice setup, add a New Choice Resource with theses values 
Screenshot 2022-11-16 at 9.29.51 AM.png
  •  Add Label and API Names to this screen 
Screenshot 2022-11-16 at 9.33.49 AM.png
  • Assignment( Assign Selected Languages):
Screenshot 2022-11-16 at 9.36.28 AM.png
  • Apex Action ( languageAction )
    • Choose Uncategorized under Filter By Category
    • Add the apex action which was created earlier i.e “Get Service Resource Ids
Screenshot 2022-11-16 at 9.41.26 AM.png
  • Decision : (ShowCandidateScreen)
Screenshot 2022-11-16 at 9.43.38 AM.png
  • Screen(Error) : This will show error if there exists no resource which supports the languages selected.
Screenshot 2022-11-16 at 9.47.16 AM.png
  • Order the flow as suggested in the image below
Screenshot 2022-11-16 at 9.20.20 AM.png

Video: (Working Solution)

From Shantinath Patil: Google Maps in Scheduler

The out of the box Salesforce Scheduler service territory search screen only lets you search a location via Google API. However, it does not show those locations on a map, nor does it add more information about that territory. An example of such a use case would be letting customers know that route to that branch may be busy someday because of a rally happening near it!

Don’t worry! With a platform like Salesforce, and with a bit of code, we can easily create a new component which can do that. Below is an example of the outcome of such coding!

VIDEO: https://youtu.be/c01EH3_Wv0M

As you can see in the demo above, the following functionalities are achieved

  1. Google search the location (address, city name or Zip Code)
  2. Go through the list of places available around the searched address. We have hardcoded to 50 km for now
  3. Set different logos to different territories
  4. Show operating hours of that territory
  5. Show additional information about selected territory

Let’s walk through the code:

Use of Service Territory Connect API

Salesforce provides us with a nice API to look for Service Territories based on various parameters. You can find more info about this API here: https://developer.salesforce.com/docs/atlas.en-us.chatterapi.meta/chatterapi/connect_resources_service_territories.htm
In our use case, we are using workTypeGroupId, latitude, longitude and radius.

Google Location Places API

To work with Google places API, you need to have an API key. You can get more information about obtaining a key here: https://developers.Google.com/maps/documentation/places/web-service/get-api-key You can use this key in the APEX class. GoogleLocationSearchController

If you look at the code in the class, you will notice that we are making 2 API calls. The first call returns you the address details and a place id. The second call returns you the geolocation data. Geolocation data is what is used by our scheduler connect API.

Core Logic

The brains of this functionality are in the FlowLocationController class. If you could get a Google API key, you can go ahead with the code (You need to replace the Google API key). However, if you do not have the key, you can still use this code by using the drop-downs to select city, state and country picklists.

The code path from the method searchLocationLatLang follows the logic of geolocation. Similarly, the code path of the method searchLocationString follows the logic of the state country picklist.

The method getTimeSlots returns a collection of timeslots information in a map related to an Operating Hour Id. We calculate slots information in string format since we need to show it as HTML in the maps component.

One thing to note here is we are capturing additional information about the territory from the standard Description field. In addition, the logo of each territory is stored in a custom field on Service Territory: Territory_Logo__c.

Aura Components

We have created two aura components and one event to handle location selection. First, the GoogleLocationSearch component calls Google Place API to get suggestions and geolocation information. Once Google API callout is complete, we trigger an event with latitude and longitude information via GoogleLocationSearchEvt. We then capture the event in the flowLocation component to display maps information. This component has specific design attributes which you can use to configure this component in the flows.

Following is the git repository containing all the code (https://github.com/snathpatil/scheduler-Google-map). You can deploy it to any org. Please note that this will create a new remote site setting to https://maps.Googleapis.com. Also, you need to make sure that you provide FLS to the custom field on Service Territory.

References:

  1. Maps component: https://developer.salesforce.com/docs/component-library/bundle/lightning:map/example
  2. Google Places Autocomplete: https://developers.Google.com/maps/documentation/places/web-service/autocomplete

Do checkout other customisations of the Location flow component here: https://unofficialsf.com/location-screen-tips-and-tricks/

From Shantinath Patil: Appointments with Dynamic Duration

In Salesforce Scheduler, Work Type is an entity where we store information about the skeleton of an invite. It’s just a template we can use to define meeting duration, block time before/after, timeframe start/end.

In the provided flow templates, work type selection happens with the help of a work type group. You first select a work type group, then a territory, and based on these two combinations; a work type is chosen automatically. Well, not automatically! The internal logic determines work type based on linking these three objects, as shown below ERD diagram.

Since the Scheduler data model only allows to map one work type to one work type group, there can always be only one work type for the selection of work type group and service territory.

This one-to-one mapping poses a limitation for a requirement where we want to define different duration based on customers needs. The good news is, there is a way to overcome this. This blog will discuss one of the approaches to set up Scheduler data to achieve dynamic duration. The basic premise is to duplicate the work type group and work type records based on durations.

Consider a scenario where you have a premier customer and a regular customer with whom you want to set up a meeting about ‘Wealth Management’. In this scenario, let’s consider that an exclusive customer always needs an appointment with a longer duration, say 90 minutes. Other customers may need a meeting with the same topic for 30/60 minutes. We can store this information on the work type group record for now. The data setup may look something like this:

Once the data is mapped correctly via junction objects in the above format, we can then carve out the following logic:

Following is the working flow with different duration:

VIDEO LINK: https://youtu.be/fwdVoxrrpgY

As you can see based on work type group selection, the flow selects the corresponding work type and duration gets adjusted accordingly.

You may ask this may confuse the end-user! Yes, looking at the same topic titles 3 times may confuse some end users! For this, you can either build your own component to display only the appointment topic and duration to choose. This approach may sound cleaner, but there is one more solution coming up in Spring 22 release. That feature allows you to filter work type groups shown on the screen by passing record ids to the component. We will cover more on that in another blog!

From Ruchi Sharma: Setup a Topic Hierarchy

Salesforce Scheduler provides a component to display and select a work type group.
By default, this component display all the work type group records, in Spring ‘22 this component is getting the capability to filter work type group records based on specified Ids.

You can check the component documentation here.

Work Type Group Hierarchy

Imagine you are a bank manager and a user is scheduling an appointment with the bank.
While scheduling an appointment you want the user to select hierarchical work type groups to know about the appointment context.

Example – Suppose the first level work type group hierarchy consists of personal banking, wealth management, etc. Based on user selection we will display the next level hierarchy. If the user selects personal banking then the second level work type group hierarchy consists of checking saving account, loan management, etc.

You can easily display work type group hierarchy in salesforce scheduler flows with clicks, not code! 🙂

Please check below 3 level hierarchy demonstration

Let’s look at how it is done,
Configuration is the same for Outbound, Inbound, and Guest flows, illustrating for Outbound Flow.

  1. Create a self lookup in the Work Type Group object.
    This will store the parent work type group record id.
  2. For this feature, we will be using the ‘Filter by work type group Ids’ input configuration of the ‘Select Work Type Groups’ component.
  3. Add a new Assignment element called “Set filter by work type group ids as null”, after Set Topic Stage.
    And the arrows pointing to the topic screen will now point to this element.

Assignment element :
In this element, we are setting the ‘FilterByWorkTypeGroupIds’ variable as null.

OOTB flow :

After change

  1. After the assignment element adds a “Get Records” element to fetch the work type group records.
    Initially, we are assuming variable workTypeGroupId is null.
    Work_Type_Group__c is the self lookup field.
  1. Loop over fetched work type group records and append the work type group Ids in the “FilterByWorkTypeGroupIds” variable.
  1. After the loop element adds a decision element to check FilterByWorkTypeGroupIds is not null (next-level hierarchy exist) or workTypeGroupId is null (If it’s loading for the first time).
  1. If the next level hierarchy exists then add an assignment element to clear the previously selected work type group and connect this element to “Topic Screen“.
    Else redirect to “Set Appointment Stage”.
  2. Optionally to display the selected hierarchy on top of the work type group selection screen, add a ‘Get Records’ element to fetch the selected work type group record.
    Redirect the topic screen to this element.
    Then add an assignment element and create a work type group collection variable to add the work type group record.
  3. Finally, connect the above assignment element to the assignment element that we have created in step 3.
  1. To show the selected hierarchy, drag and drop the “DisplayWorkTypeGroupHierarchy” custom component above the “Select Work Type Groups” component in the topic screen.
    And pass the work type group collection variable as input to this component that we have created in step 8. (optional)

That’s it! Activate and run the flow.

All the code is bundled at https://github.com/ruchi2994/workTypeGroupHierarchy. It contains the above component and updated flow.

From Shantinath Patil: Schedule a support advisor

Let’s think of you as a support agent. You get a call from a customer asking for an appointment for some questions he has on the case and wants to talk with the case owner. So what can do you What will you do in this “case?”

Salesforce Scheduler can help you in this “case”. Let’s drill down on what all we need to accomplish this:

  1. Case owner as a Service Resource
  2. Case reference to Appointment
  3. A flow
  4. Quick action on Case to initiate the flow.

A logical flow of the approach we will take in this use case will look like the following:

Get Case Details

Since this flow will run on the case, we need to get the details of it. First, we can set the “recordId” input parameter to get the current case, automatically giving us the case Id. Then, using this variable, we can query the case using the “Get Records” element in the flow.

Note that we need AccountId to map it to the ParentRecordId of the Service Appointment. OwnerId is needed to get the related Service Resource.

If we want to tag the current CaseId, we can create a new lookup on Service Appointment. A custom field is needed since Service Appointment can only support Account, Lead, and Opportunity except for Case! To ensure that value is populated in that lookup, we can override the JSON we pass in the final Save Action defined in the flow. For more details, you can refer to the blog here: https://unofficialsf.com/build-your-own-appointment-review-screen/. For now, we can skip it.

Get Service Resource

If we want to set the case owner as of the person who will work on this case, we need to make sure that he is a service resource. For more details, please check this help doc: https://help.salesforce.com/s/articleView?id=sf.ls_create_resources.htm&type=5.

To fetch the service resource, we can add the “Get Records” element like below. In this, we are fetching Service Resource based on Case OwnerId.

Leverage filterByResourceIds

Scheduler flow allows you to filter the resources based on the list of Ids you pass to it. You can read more details here: https://help.salesforce.com/s/articleView?id=sf.ls_enable_service_resource_filtering.htm&type=5.

We can leverage this functionality for our use case. Since we want our case owner to be the Service Resource, we can add the Service Resource Id from the previous step to filterByResourceIds. Additionally, we can make anonymous booking as true and resource booking as false. This will make sure that we skip the OOB triage screen and resource selection screen.

The advantage we get from this is if the case owner is not a Service Resource, existing logic will fetch any available resource anonymously. You will not have to worry about resource selection. End to end experience will be very seamless for the agent who is booking that appointment.

Once all the nodes and assignment is done, you can test the flow using the “Debug” option and activate it once it’s working as expected.

Setup

After this step, it’s up to you how you want to distribute the flow. For this blog, we are using Case Action to create a new appointment. Since the “recordId” flow variable is set for “Available for input” in the flow, this action will automatically set it to the current record on the detail page.

You can add this action to the case layout. The end-to-end flow should work something like this:

We have added all the metadata to this git repo for your reference. Please feel free to deploy it to your Sandboxes and try it out! Link: https://github.com/snathpatil/case-appointment-booking

Note if you’re looking for a virtual advisor then do have a look at this blog: https://unofficialsf.com/tele-or-virtual-scenarios-using-salesforce-scheduler/

Scheduler is not to be positioned in cases where the Service Resource is travelling to a customer’s location

From: Saikiran Gogurla: Spring 21 Feature: Preset the Territory’s Timezone on the timeslot screen

The default time zone for all times shown in a Salesforce Scheduler flow would be the timezone set for the user scheduling the appointment. See more details on how this would work for the various flows here.

In Spring 21 feature, a feature provided an ability to (pre)set the timezone across multiple screens on the Outbound, Inbound & Guest User flows (Create & modify) across mobile & desktop.

One common scenario this helps you to cover is, when a Customer care agent is booking an “On-behalf” appointment and wants to see the time-slots in the timezone of their selected territory.

Now, this is easy to achieve with Clicks, not code!


Imagine a customer care agent based in Chicago (Central time) is booking an appointment for a customer who wants to meet a banker in the Denver branch (Mountain time) of Cumulus bank.

Wouldn’t the customer care agent wants to see the time slots in Mountain time automatically and book the appointment?


With Salesforce Scheduler, now the agent can easily look at the available time slots in the bank operating hours timezone by default and book the appointments. Don’t worry about the Timezone in which the territory is operating and all; it is taken care of!!
All the timings shown in my flow are configured to Cumulus Bank (Denver) operating timezone.

Question: What if some other bank is at a different location or any Service Territory for that matter? Will my flow behave like it is configured to that Service Territory dynamically?

Answer: Oh Yes!!

Let’s look at how it is done,

Configuration is the same for Outbound, Inbound, and Guest flows, illustrating for Outbound Flow.

  1. Add a new Assignment Element called Set Territory Timezone, after the Location Screen.
image
  1. Set the DefaultTimeZone variable to Timezone from ServiceTerritory Operating Hours.
Config3.png
  1. Save the flow.

— Now the flow is configured to the timezone based on the Service Territory chosen —

Screens that have an impact :

  1. Candidate Screen now shows Next Availability in the Service Territory Timezone.
Candidate Screen.png
Debug Item.png
  1. Flow Time Slot Screen, now by default shows Time Slots in Territories Timezone.
Time Slot Screen.png
  1. Flow Review Screen shows the selected time slot in the timezone in which Territory Operates.
Review Screen2.png
  1. For Inbound and Guest Flow., Next Availability and Time Slots are configured to Territory Timezone on Resource Time Slot Screen, and it looks like,
Screenshot 2021-06-09 at 5.11.57 PM.png

These are some of the use-cases that can be solved but not limited to,

  • Agents booking on-behalf appointments, want to see all time-slots in territory timezone by default.
  • Branch Managers and above, no matter from where customer books an appointment, he/she visits our site and tries to book appointment and all time-sots should be shown in my Territory supported Timezone by default.
  • Guest appointment booking, are you sending out an email to customer to book an appointment using the link and wants him to see time-slots in your territory timezone?

Your customer will never miss his/her appointment time, when booked with Territory timezone!

From Shantinath Patil: Email signature booking

THIS CAPABILITY IS COMING WITH THE SALESFORCE SCHEDULER PRODUCT IN SUMMER 22 RELEASE!! CHECK IT OUT

Ever wondered if you can offer a link to your customers to book an appointment with you? Or a situation where you want everything to be preselected, and you will immediately jump to the timeslot screen? Well, wait no more! In this blog, we will show you how you can achieve this!

Let’s consider the first scenario. You give a link to a customer OR add a link to your email signature. Here is what you have to do.

Part 1: Prepare!

We need to build a flow such that our landing page should be the timeslot screen. To make that work, we should be able to populate all the required values. In current out of the box flows, all the screens we traverse are used to populate values required for the timeslot screen. Let’s see what all values we need to populate.

  • Work Type Group: This is the outcome from the OOB Work Type Group Selection page. This helps the internal logic to get which topic/template we are trying to book.
  • Service Territory: This is the location of your appointment.
  • Service Resource: This is the resource that will cater to the appointment request.

Since these values are needed as inputs for our flow, we can create flow variables and mark them available for input.

image.png

Once all these flow variables are created, we can create the assignment for those. Note that we need to assign Service Territory Id to a Service Appointment instance. This makes sense if you look at location screen output. That screen is giving selected territory output as “{!ServiceAppointment.ServiceTerritoryId}”. And since we cannot just set one field on the Service Appointment instance, we will have to initialise all other fields as an empty string. For this, we can refer to the OOB assignment stage.

image.png

The next step is to link all required values to the timeslot screen. Again, we can refer to the OOB timeslot screen configuration. We should be doing the only change to pass our input flow variables for Service Resource and Work Type Group.

image.png

After that, configure the review screen. Make sure you map all the required attributes.

image.png

And finally, we should configure the save action. Here you can pass “{!serviceAppointmentFields}” as input, which is an output of the Review Screen. For now, we are considering invoking this flow for guest flow, so we are adding Lead as input as well.

image.png

Optionally you can configure the confirmation screen. Done! Your flow should be ready to be tested. Save and debug the flow by providing the input attributes!

image.png

Part 2: Distribute!

In a nutshell, we created a flow that takes all the parameters needed for the TimeSlot screen and process it. Once such a flow is ready, we can then use any mechanism of distribution of the flow.

Here is the link to Salesforce documentation which explains to you how can we create a flow page in the community and pass values: https://help.salesforce.com/articleView?id=sf.flow_distribute_internal_url_variable.htm&type=5

This is not the only way! You can even embed the flow in a lightning component and embed that to an external website. More detail: https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/components_using_flow_inputs_set.htm

If you take the approach of exposing this flow in a public community, consider adding a ReCaptcha. More details: https://unofficialsf.com/protect-a-flow-on-a-public-community-with-the-google-recaptcha-component/

The below demo shows all things in action. You can see that a service resource has sent details to a prospect related to Salesforce Scheduler. That email includes a link. Once the user clicks on the link, they will get redirected to our public community page, which hosts the flow we created from the above steps. All the user have to do it to select a slot, add their details and submit!

Part 3: One More Thing!

Now that we have created a flow that takes input, we surely don’t expect our Service Resources to generate the link manually! Besides, it’s easier to use existing components to build a flow that will give output as an URL to proudly put in their email signature or give to customers! Heck, they can even include that in their digital business cards!

To do this, we will reuse existing OOB components. It’s like the Service Resource will perform initial actions on behalf in OOB flow on behalf of the end-user. Following are the steps to create such a flow:

  1. We will configure the “Generate Signature URL” button on the Service Resource page. From there, one can initiate the signature generation flow. Hence we need “recordId” as an input flow variable.
  2. We will pass this record id to the existing WorkTypeGroup selection page. This will ensure that only the WTGs which are related to that resource are displayed.
  3. Next, configure the Service Territory screen. Here we need Service Resource Id and WTG Id, which will be the output of the previous screen. Having both the inputs will ensure that search results for the territory will give us the correct territory.
  4. Finally, we will generate the URL from all the selection with the help of a formula flow variable. The formula looks like this:
image.png

Note that we have considered the scenario that actual booking flow is configured in a community, hence the “communityURL” variable.

Once you build out the flow, it looks like this:

image.png

Test this flow in the flow debug, and once done, activate it. You can configure a new quick action on Service Resource and add that to its layout.

Both the flows are added to this git repo:https://github.com/snathpatil/signatureflow. You can deploy it to your salesforce org and validate!

From Shantinath Patil: Pre-filling values on the Appointment Review Screen

So you want to preload some values on the final Review screen provided by Scheduler?

Check this out…


Let’s say you want to prefill the email & phone details.

  1. Set the Email and Phone in the first assignment stage:
image (22).png

2. Assign Service Appointment record variable in the review screen. In an Outbound flow template that input is blank and that’s why making changes only mentioned in step 1 will not work.

image (23).png

That’s it!

image (24).png

Note this method works for the Outbound flow & Inbound flow and is restricted to the following fields:

  • “AdditionalInformation”
  • “AppointmentType”
  • “Comments”
  • “ParentRecordId”
  • “ServiceTerritoryId”
  • “Street”
  • “City”
  • “State”
  • “Country”
  • “PostalCode”
  • “SchedStartTime”
  • “SchedEndTime”
  • “WorkTypeId”
  • “Id”
  • “Description”
  • “Subject”
  • “Phone”
  • “Email”
  • “ContactId”
  • “IsAnonymousBooking”

To prefill other fields or for other flows (eg Guest Flow), you need to do it build your own screen & logic: https://unofficialsf.com/build-your-own-appointment-review-screen/

Also check out other cool changes that you could with the review screen here: Review Appointment screen: Quick Flow Customisations – Salesforce Scheduler

From Chris Albanese: Summer 21 Feature: Get Resources and Available Time Slots Through New Apex Methods

Salesforce Scheduler introduced a new feature in the Summer ’21 Release which allows developers to easily make custom time slot screen flow components that interact with external systems. The new Apex methods call the Get Appointment Candidates and Get Appointment Slots APIs. This capability helps you easily get all the service resources and available time slots or get available slots for a resource.

With this new feature developers have the choice of using the Scheduler REST APIs and the Scheduler Apex methods. The REST APIs are a powerful tool that allows you to develop custom user interfaces and applications that run on your own websites. The Apex methods offer a great option for developers building custom UI components and processes that are run by users that are already logged into Salesforce, such as running a Lightning Flow launched from a button on the Account Screen or viewing a page inside of a Salesforce Experience site.

Check this out below.

Calling the Scheduler Get Appointment Candidates API with Apex

Here’s a little concept illustrator to show you how easy it is to call the API using Apex.

Let’s say you want to ask Scheduler to return the next time slot available for a Work Type Group and Territory. The user is a call center user, talking to a customer and the customer says “give me your next available appointment please”.

Solution Architecture

  • A simple Flow embedded on the account page
  • The flow calls an @invocableMethod which uses the new API
  • The method returns the 1st time slot found
  • The flow creates a service appointment using the time slot returned

User Experience

The top left of the 2 screen shots below illustrate how the call center user can ask the scheduler to return the next available time slots and create a service appointment.

image.png
image.png

Screen Flow

As you can see in the screen shot below, the flow is pretty simple. It takes in the account id from the Account Page. It prompts the user for Work Type Group, Territory and Earliest Start Time. It passes these parameters into the Apex Action. If a slot is returned, it creates a service appointment, if not, an informational message is displayed.

image.png

Apex Class

As you probably already know, you can call Apex Methods from a Lightning Flow using the Action component. There are lots of great articles and tips on building @invocableMethods, which I won’t go into here. For this example, the method I created takes in the parameters, calls the Scheduler Apex Method named lxscheduler.SchedulerResources.getAppointmentCandidates, and returns the first result found, or returns nothing if no slots are found.

Receiving the Input Parameters from the Flow

In the example code below, to accept the input parameters from the flow, I created a class called payloadIn. It contains variables for Work Type Group Id, Service Territory Id, Scheduling Policy Id, Account Id and Start Date Time. We use these values to call the Scheduler Method. Variables like Account Id are not mandatory, I’ve just included it in my example.

Setting the Input Parameters in the Apex Method

The Get Appointment Candidates API provides an easy to use class to set the parameters that you want to pass to the Get Appointment Candidates API, lxscheduler.GetAppointmentCandidatesInput. As you can see in the example code, I simply set the parameters to the values passed in from the flow. I’ve hard coded the EndTime to be the Start Time plus 3 days. You can of course change this to be more dynamic.

Calling the Apex Method

It is super simple and is represented by this line in the example code:

String response = lxscheduler.SchedulerResources.getAppointmentCandidates(input);

Note, that no authentication code, connected apps, named credentials and other items are required to call the Apex Method. Since the running user is already authenticated, they can call the Scheduler API, just like they can call any other Apex Method they have access to.

Hooray! This is what is so awesome about this new API and makes code based development for Scheduler so much easier for these types of use cases.

Parsing the Results – they’re different from the REST API results

The method returns the results as a JSON string. I won’t go into the ins and outs of JSON and you can certainly find out more with a quick web search, but what you need to know is that the results from the lxscheduler.SchedulerResources.getAppointmentCandidates call are slightly different from the REST API call.

The REST API call for getAppointmentCandidates returns this format:

{
"candidates" : [ {
"endTime" : "2019-01-23T19:15:00.000+0000",
"resources" : [ "0HnB0000000D2DsKAK" ],
"startTime" : "2019-01-23T16:15:00.000+0000",
"territoryId" : "0HhB0000000TO9WKAW"
}, {
"endTime" : "2019-01-23T19:30:00.000+0000",
"resources" : [ "0HnB0000000D2DsKAK" ],
"startTime" : "2019-01-23T16:30:00.000+0000",
"territoryId" : "0HhB0000000TO9WKAW"
}, {
"endTime" : "2019-01-23T19:45:00.000+0000",
"resources" : [ "0HnB0000000D2DsKAK" ],
"startTime" : "2019-01-23T16:45:00.000+0000",
"territoryId" : "0HhB0000000TO9WKAW"
}]
}

The Apex Method for getAppointmentCandidates returns this format:

{{
"endTime" : "2019-01-23T19:15:00.000+0000",
"resources" : [ "0HnB0000000D2DsKAK" ],
"startTime" : "2019-01-23T16:15:00.000+0000",
"territoryId" : "0HhB0000000TO9WKAW"
}, {
"endTime" : "2019-01-23T19:30:00.000+0000",
"resources" : [ "0HnB0000000D2DsKAK" ],
"startTime" : "2019-01-23T16:30:00.000+0000",
"territoryId" : "0HhB0000000TO9WKAW"
}, {
"endTime" : "2019-01-23T19:45:00.000+0000",
"resources" : [ "0HnB0000000D2DsKAK" ],
"startTime" : "2019-01-23T16:45:00.000+0000",
"territoryId" : "0HhB0000000TO9WKAW"
}
}

Did you notice the difference? The outer wrapper for “candidates: [ …]” is missing from the results, so you need to make sure you deserialize the results using the proper format.

Here’s the class I used to deserialize the results:

public class schedulerCandidates {

public datetime startTime;
public datetime endTime;
public List<String> resources;
public String territoryId;


public static list<schedulerCandidates> parse(String json) {
return (list<schedulerCandidates>) System.JSON.deserialize(json, list<schedulerCandidates>.class);
}
}

Example Code

public with sharing class LXScheduleFirstAvailable {

@invocableMethod(label='Schedule to First Available' description='Schedule to whomever is next')
public static list<event> ScheduleIt(list<payloadIn> payloadList ){
if(payloadList == null || payloadList.size() <> 1) return null;
payloadIn pl = payloadList[0];
lxscheduler.GetAppointmentCandidatesInput input = new lxscheduler.GetAppointmentCandidatesInputBuilder()
.setWorkTypeGroupId(pl.workTypeGroupId)
.setTerritoryIds(new List<String>{pl.serviceTerritoryId})
.setStartTime(pl.startDateTime.format('yyyy-MM-dd\'T\'HH:mm:ssZ'))
.setEndTime(pl.startDateTime.addDays(3).format('yyyy-MM-dd\'T\'HH:mm:ssZ'))
.setAccountId(pl.accountId)
.setSchedulingPolicyId(pl.schedulingPolicyId)
.setApiVersion(Double.valueOf('50.0'))
.build();
//call the Apex Method - no REST API authentication or API user needed!!!
String response = lxscheduler.SchedulerResources.getAppointmentCandidates(input);
//parse the results using JSON.deserialize
if(response==null) return null;
list<schedulerCandidates> allslots = schedulerCandidates.parse(response);
//slots found, return just the first one
if(allslots!=null) {
event thisevent = new event();
thisevent.startdatetime = allslots[0].startTime;
thisevent.enddatetime = allslots[0].endTime;
thisevent.description = allslots[0].territoryId;
thisevent.whoid = allslots[0].resources[0];
return new list<event>{thisevent};
}
//no slots found, return null
return null;
}
public class payloadIn{
@invocableVariable(required=true)
public string workTypeGroupId;
@invocableVariable(required=true)
public string serviceTerritoryId;
@invocableVariable(required=true)
public string schedulingPolicyId;
@invocableVariable(required=true)
public string accountId;
@invocableVariable(required=true)
public datetime startDateTime;
}

}

Example Code Test Method

@isTest
private class LXSchedulerFirstAvailableTest {
static testMethod void getAppCandidatesTest() {
String expectedResponse = '[' +
' {' +
' \"startTime\": \"2021-03-18T16:00:00.000+0000\",' +
' \"endTime\": \"2021-03-18T17:00:00.000+0000\",' +
' \"resources\": [' +
' \"0HnRM0000000Fxv0AE\"' +
' ],' +
' \"territoryId\": \"0HhRM0000000G8W0AU\"' +
' },' +
' {' +
' \"startTime\": \"2021-03-18T19:00:00.000+0000\",' +
' \"endTime\": \"2021-03-18T20:00:00.000+0000\",' +
' \"resources\": [' +
' \"0HnRM0000000Fxv0AE\"' +
' ],' +
' \"territoryId\": \"0HhRM0000000G8W0AU\"' +
' }' +
']';
lxscheduler.SchedulerResources.setAppointmentCandidatesMock(expectedResponse);
Test.startTest();
LXScheduleFirstAvailable.payloadIn pl = new LXScheduleFirstAvailable.payloadIn();
pl.workTypeGroupId = '0VSB0000000LB9TOAW';
pl.serviceTerritoryId ='0HhB0000000TrsdKAC';
pl.startDateTime = datetime.now();
pl.accountId = '001B000001KYUM3IAP';
pl.schedulingPolicyId = '0VrB0000000Kz6Z';
list<LXScheduleFirstAvailable.payloadIn> plList = new list<LXScheduleFirstAvailable.payloadIn>();
plList.add(pl);
List<event> candidateList = LXScheduleFirstAvailable.ScheduleIt(plList);
System.assertEquals(1, candidateList.size(), 'Should return only 1 record!');
Test.stopTest();
}
}

Summary

This new Apex API is awesome and simplifies calling the Scheduler API for use cases where the user is already logged into Salesforce.

Additional Notes about this Example

In my example flow, I’m using 2 create record steps to create the Service Appointment and Assigned Resource. In production, you probably want to use the Scheduler flow component called Save Appointment. I’ve described how to use that component here. This component gives you added capabilities to ensure that the time slot selected is still available at the time of record save.

From Chris Albanese: Your Branding on the Salesforce Scheduler Flows

Salesforce Scheduler flows are just flows. You can supplement the screens with any information you want with simple configuration.

image.png

Adding a display field in the same Screen element where the Select Work Type Groups is. The example includes merge fields and an image field.