© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
J. M. RhodesCreating Business Applications with Microsoft 365https://doi.org/10.1007/978-1-4842-8823-8_22

22. Creating a Reservations Booking Solution in Power Apps, SharePoint, and Power Automate

Jeffrey M. Rhodes1  
(1)
Colorado Springs, CO, USA
 

This chapter continues our objective of putting multiple pieces of the Microsoft 365 environment together into cohesive solutions. It stemmed from a customer wanting to try the Microsoft Bookings application but that not being approved for use. I suggested using our familiar combination of SharePoint and Power Apps. For our sample here, I added the automatic scheduling of an associated Teams meeting via Power Automate. It has a few advantages over Bookings, such as being able to schedule on behalf of another user and the ability to offer multiple appointments at the same time.

Configuring the SharePoint List

Figure 22-1 shows the SharePoint list where we store our data.

A screenshot of the reservations page. It has 8 columns with relevant data underneath.

Figure 22-1

SharePoint list storing reserviation information

Importantly, we include the time component in our AppointmentDate column since the user will be picking from available dates and times. We include Modified By in our view since we are going to allow either the person who created the appointment1 OR the person for whom it was scheduled edit the appointment. We include a Subject and Comments for the user to give some additional detail on what they want to discuss in the reservation. Status is a choice column with the default value of Available. Our Power App will change it to Booked when the appointment is scheduled. If the user cancels, it will put it back to Available and clear the Subject and Comments. We store the Participant (Person column) separately so a user can schedule for someone else as mentioned before2. We default AppointmentLength to 20 (minutes) but preserve the ability to change this value if desired. Finally, we have a ScheduledTeamsMeeting Yes/No column. We will use that later with our scheduled Power Automate flow. We will query for all the Booked reservations where ScheduledTeamsMeeting is No. The flow will then schedule the meeting and set this value to Yes.

Next on the docket is Power Apps.

Creating our Application in Power Apps

Our first task is to set a variable in during the application’s OnStart event, as shown in Listing 22-1.
Set(
    varUserEmail,
    User().Email
);
Set(
    varParticipant,
    Blank()
)
Listing 22-1

OnStart to set initial variables

We will use varUserEmail later in determining if there are existing reservations the current user can edit. varParticipant is useful in updating the participant as well as showing or hiding our error label. We initialize it to be Blank().

Figure 22-2 shows the Home screen of our application.

A screenshot of the appointments page. It has available appointments on the left, and input boxes to add a new appointment on the right.

Figure 22-2

Adding an appointment

As we have seen in other examples, we use a Gallery control to view and select the available records. In this case, it shows the available appointments. We filter our Reservations list to only show available ones in the future by setting the Items property:
Filter(Reservations, Status.Value = "Available" && AppointmentDate > Now())
Note that we need to say Status.Value because Status is a Choice column. We use Now() rather than Today(), since only the former includes the current time as well as the current date. We display the appointment date and time in the “long” date format we want by using the DateTimeValue() method together with the Text() method, which has a second parameter to format the date and time in the Wednesday, Sep 07, 2022 09:00 AM format. Here is the label’s Text property:
Text(DateTimeValue(ThisItem.AppointmentDate), "dddd, mmm dd, yyyy hh:mm AM/PM")
To give the appointment length value a context, we concatenate it with a label and units in the second label’s Text property:
"Length: " & ThisItem.AppointmentLength & " minutes"
To set the entire selected item to yellow, we set the gallery’s TemplateFill property to:
If(ThisItem.IsSelected, Yellow, RGBA(0, 0, 0, 0))
When the user selects an appointment slot, we store it in our varSelectedApt variable in the gallery’s OnSelect event:
Set(varSelectedApt, Gallery1.Selected)
Since we want to reserve our screen real estate for our appointments and for comments, we use a combo box for our participant. We set its Items to be:
Office365Users.SearchUserV2({searchTerm: ParticipantBox.SearchText, top:50}).value
We call the SearchUserV2() method from Office365Users, passing in the SearchText of the combo box. That method returns a record, but its value is the table that the box wants. We set the DisplayFields and SearchFields properties to both be ["DisplayName"] so the user’s name displays and searches correctly. We also set isSearchable to true and SelectMultiple to false. It will be useful to have the selected user stored in the varParticipant variable, which we set in both the combo box’s OnSelect and OnChange events3:
Set(varParticipant, ParticipantBox.Selected)
We have an ErrorLabel to remind the user what they need to do before confirming the appointment. Since we need a participant and a Subject as a minimum, we set its Visible property to:
IsBlank(varParticipant) Or SubjectBox.Text = ""
To write our reservation back to the SharePoint list, we use our old friend the Patch() method, as shown in Listing 22-2.
Patch(
    Reservations,
    LookUp(
        Reservations,
        ID = varSelectedApt.ID
    ),
    {
        Subject: SubjectBox.Text,
        Comments: CommentsBox.Text,
        Status: {Value: "Booked"},
        Participant: {
                '@odata.type': "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser",
                ODataType: Blank(),
                Claims: Concatenate(
                    "i:0#.f|membership|",
                    varParticipant.Mail
                ),
                DisplayName: varParticipant.DisplayName,
                Email: varParticipant.Mail,
                Department: varParticipant.Department,
                JobTitle: varParticipant.JobTitle,
                Picture: ""} ,
                ScheduledTeamsMeeting: false
    }
);
Reset(ParticipantBox);
Reset(SubjectBox);
Reset(CommentsBox);
Set(
    varParticipant,
    Blank()
)
Listing 22-2

OnSelect code for the Confirm Appointment button

This is less complicated than some of our other examples as we don’t need to create any new records. Instead, we use the LookUp() method to find the correct SharePoint list item. We then set the Subject and Comments to the values entered in their respective controls. Setting the Status is non-trivial, though, since it is a Choice column in SharePoint. We normally just match it to a drop-down box, but in this case we always want to always set it to Booked. I originally tried to create a variable from a hidden drop-down box, but that was strangely unreliable. Luckily, a quick search found the proper {Value: “Booked”} syntax. We’ve covered how to update a SharePoint Person column, so we won’t cover it again here other than to note how we took advantage of our varParticipant variable. The ScheduledTeamsMeeting column should already be false, but we set it that way to be on the safe side. We don’t set AppointmentLength since that is part of the appointment and not set in our design by the person scheduling. We Reset() our controls so they are empty if the user selects another appointment or returns after editing an appointment. We also set our varParticipant to Blank() to be sure that it doesn’t persist now that we have cleared the combo box where we set it.

Our design is to only show the Existing Appointments button if the user has a future appointment. So we set its Visible property, as shown in Listing 22-3.
With(
    {
        ds: Filter(
            Reservations,
            ((Participant.Email = varUserEmail || 'Modified By'.Email = varUserEmail) && Status.Value = "Booked" && AppointmentDate > Now())
        )
    },
    CountRows(ds) > 0
)
Listing 22-3

Visible property for the Existing Appointment button

This is a bit different than our normal syntax, but my original formulation4 was subject to delegation issues. Instead, we use the With() function to evaluate our filter and treat it as a single record. As we described in the last section, we want for either the participant or the person who made the reservation to be able to edit it. Since both Participant and Modified By are Person columns, we can get their Email property and compare it to the current user’s email. We also wanted our Status to be Booked and for the appointment to be in the future.

When the user clicks the Existing Appointments button, we want to go to the ModifyAppointment screen and thus have this in its OnSelect event:
Navigate(ModifyAppointment)
Figure 22-3 shows the resulting screen.

A screenshot of the appointments page. It has existing appointments on the left, with input boxes to update an appointment on the right.

Figure 22-3

Updating or deleting an appointment

Our gallery’s Items property uses the same Filter() as our Existing Appointments button to get future appointments for which the user is either the participant or the one to set up the appointment:
Filter(
    Reservations,
    (Participant.Email = varUserEmail || 'Modified By'.Email = varUserEmail) && Status.Value = "Booked" && AppointmentDate > Now()
)
As is our standard practice, we set a variable5 in the OnSelect event when the user picks an appointment:
Set(varSelectedModifiedApt, ExistingAptsGallery.Selected)
Since the user can edit the existing Subject or Comments, we link their Default properties to the selected appointment:
varSelectedModifiedApt.Subject
and
varSelectedModifiedApt.Comments

This will display the existing values that the user can then edit if desired. This is helpful as we will write the contents of these boxes back to the appointment (even if they haven’t changed).

The rest of the logic is in the two buttons. Listing 22-4 shows the OnSelect code for the Delete Appointment button.
Patch(
    Reservations,
    LookUp(
        Reservations,
        ID = varSelectedModifiedApt.ID
    ),
    {
        Subject: "",
        Comments: "",
        Status: {Value: "Available"},
        ScheduledTeamsMeeting: false
    }
);
Navigate(Home)
Listing 22-4

OnSelect code for the Delete Appointment button

We want to Patch() the selected appointment by clearing its Subject and Comments, setting its Status back to Available, and setting ScheduledTeamsMeeting to false. We’d like to clear the Participant as well, but that is not currently possible. But since we will write a new Participant when the appointment is rescheduled, we should be fine.6 We then navigate back to the Home screen.

The logic for the Update Appointment button is similar, as shown in Listing 22-5.
Patch(
    Reservations,
    LookUp(
        Reservations,
        ID = varSelectedModifiedApt.ID
    ),
    {
        Subject: UpdatedSubjectBox.Text,
        Comments: UpdatedCommentsBox.Text
    }
);
Navigate(Home)
Listing 22-5

OnSelect code for the Update Appointment button

We update both the Subject and the Comments and then return to the Home screen.

We are now ready to go to Power Automate to schedule a Teams meeting for each booked appointment.

Adding a Scheduled Flow in Power Automate

While we could have launched Power Automate from within our application, in this case, it makes more sense to have a scheduled flow, as shown in Figure 22-4, perhaps every day at 5 AM.

A cropped screenshot of the power automate page has a panel on the left with my flows highlighted. An arrow points to the scheduled cloud flow option from the menu on the right.

Figure 22-4

Creating a Scheduled cloud flow

That will minimize the rework if the user cancels or changes the appointment the same day. While we will schedule a Teams meeting for each future appointment, we could add additional logic to only schedule meetings one week or some other time frame before the appointment. Figure 22-5 shows how we set the frequency and the time for the flow to run.

A screenshot of the build a scheduled cloud flow window. It has input boxes for flow name, run this flow, and repeat every, option on the right.

Figure 22-5

Scheduled cloud flow settings

After setting up this recurrence, we go to the SharePoint connector and configure the Get items action, as shown in Figure 22-6.

A screenshot of the get items window has drop-down menus for site address, list name, and include nested items. It also has a select a folder option.

Figure 22-6

Calling the Get items SharePoint action

We configure the Site Address and List Name to match our connection in Power Apps. We then need to click Show advanced options in order to choose which items we want. We do that by entering an Open Data Protocol (OData) filter query, as shown in Figure 22-7.

A screenshot of the get items query. An arrow points to the data u t c now in filter query from the expression tab on the right.

Figure 22-7

Configuring the Filter Query in our Get items query

This has a different syntax than what for many of us is a more familiar Standard Query Language (SQL) query, but the concepts are the same. In words, we want to find the list items that have been booked, are in the future, and have not already been scheduled as Teams meetings. Here is the text of the OData query:
(ScheduledTeamsMeeting ne '1') and (AppointmentDate ge '@{utcNow()}') and (Status eq 'Booked')

ne means “not equals,” ge means “greater than or equals,” and eq means “equals.” Notice also how a 1 represents true and a 0 represents false. We use the Expression window in Figure 22-7 to insert the utcNow() method to dynamically insert the current time in Universal Time Coordinated (UTC) format (previously known as Greenwich Mean Time). Since our AppointmentDate value from SharePoint includes a time zone component, Power Automate gets the math right on whether the time is in the future. We could include an OData Order By value, but we are going to process all the list items we get and thus the order does not matter.

We next need to loop through all the list items that are returned by the query. As shown in Figure 22-8, we do this with the Apply to each action and use the values attribute, which is the list of the SharePoint items.

A cropped screenshot of the apply to each task window. An arrow points to value in select an output, from the dynamic content tab.

Figure 22-8

Apply to each loop through the SharePoint list items

The next part takes some trial and error. The AppointmentDate value from the list items at first looks to be right in that the format is of this form7:
2022-08-24T17:00:00Z
But using this value directly when we schedule the Teams meeting causes an error. Instead, we use the Compose action, as shown in Figure 22-9, and then use the formatDateTime() expression.

A screenshot of the format start date time task under apply to each window. An arrow points to format date time input, from the expression tab on the right.

Figure 22-9

Formatting the start date and time

Getting the syntax for the AppointmentDate is not trivial:
formatDateTime(items('Apply_to_each')['AppointmentDate'], 'yyyy-MM-ddTHH:mm')
The key part is items('Apply_to_each'). This will be the name of the loop and could by 'Apply_to_each2' or a similar name. The second parameter is the date format, which means four-digit year, two-digit month, two-digit date, time zone, two-digit hour in a 24-hour format, and two-digit minute. Looking at our Run history, the output of our Compose action is:
2022-08-24T17:00

So what we needed to get rid of was the seconds and the Z (which means the time zone is UTC).

We similarly need the ending date and time of our meeting. We again use the Compose action, as shown in Figure 22-10.

A screenshot of the format end date time task under apply to each window. An arrow points to format date time input through add minutes method.

Figure 22-10

Formatting the end date and time using the addMinutes method

We again use the formatDateTime() expression but also add in the addMinutes() expression to incorporate our AppointmentLength column:
formatDateTime(addMinutes(items('Apply_to_each')['AppointmentDate'], int(items('Apply_to_each')['AppointmentLength'])), 'yyyy-MM-ddTHH:mm')
Notice how we have to also use the int() method to format the value as an integer. That gives us a value that looks like this:
2022-08-24T17:20
We are now finally ready to schedule our meeting. We select the Create a Teams meeting action and choose our primary Calendar from the list of available calendars. Figure 22-11 shows how we use the Participant Display Name value from our SharePoint list item in the Subject of the meeting.

A screenshot of the create a teams meeting task window. An arrow points to the participant display name data under the subject input box, from the dynamic content tab.

Figure 22-11

Setting the Teams Meeting Subject

We use a similar technique (Figure 22-12) to add the Subject, Comments, Participant, and Scheduled By to the Message of the meeting.

A screenshot of the create a teams meeting task window with data in message input box. An arrow points to the outputs data under the start time input box.

Figure 22-12

Setting the Teams Meeting Message and Start Time

Notice how we use the HTML Line Break (<br />) to get hard returns in the message. We use the Outputs from our Compose action (Figure 22-9) for the Start time and do the same for the End time (see Figure 22-13).

A screenshot of the create a teams meeting task window. An arrow points to the participant email data under the required attendees input box.

Figure 22-13

Setting the Teams Meeting Required attendee

Our last scheduling task is to add our Participant Email as a Required Attendee, as shown in Figure 22-13.

Now that we have scheduled the meeting, we need to make sure the next time the flow runs that it doesn’t try to schedule another one. To do that, we need to use the SharePoint Update item action to set the ScheduledTeamsMeeting column to Yes, as shown in Figure 22-14.

A screenshot of the update item task window. Data from I d, status value, and scheduled teams meeting are highlighted as I D, booked, and yes.

Figure 22-14

Updating the SharePoint Item to set ScheduledTeamsMeeting to Yes

Notice how use the ID from our Get items action (within our loop) in order to make sure we are editing the right item. We set the Status to Booked just to be on the safe side and then set ScheduledTeamsMeeting to Yes.

We did it! Figure 22-15 shows an example of our scheduled Teams meeting.

A screenshot of the Microsoft teams meeting reservation. It has the details of a reservation in the scheduled team meeting for Jeffrey Rhodes.

Figure 22-15

Scheduled Teams Meeting reservation

This solution doesn’t cancel the meeting if the user cancels the reservation in our Power App, but that would be a good enhancement. We could also easily connect Power BI to our SharePoint list to make a dashboard showing appointments, what percentage of available appointments were booked, and much more.

Summary

In this chapter, we connected Power Apps to SharePoint to keep track of appointment reservations. We incorporated some specialized logic to allow either the submitter or the person with the registration to edit or cancel their reservations. We then used the ability of Power Automate to do scheduled flows to automatically create Teams Meetings for the appointments. To pull this off, we learned how to do OData filter queries so that we could limit to just those appointments without a Teams Meeting as well as some gyrations around date and time formats. We then updated our SharePoint list to reflect that the meetings had been scheduled so we could avoid duplicates. In the next chapter, we will dig into Power Apps Forms control for more sophisticated adding and editing of data.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.137.91