In the last months we have seen an increasing demand from our healthcare organizations to provide self scheduling functionality.
An example is how hospitals want to provide a safe way for their patients to get tested for Covid-19, or how to visit your family member while keeping a safe distance and prevent crowding.

Luckily I have the opportunity to work with hospitals who already use our technology, and providing such a solution is then nothing more then combining different pieces to get a scalable solution.

In a couple of days, we created a solution that leverages the Microsoft Health Bot, Office 365 Shifts and the Microsoft Graph!
Let me show you how this works

Microsoft Health Bot Service

With the Microsoft Healthbot Service you can easily create your scenario to provide self scheduling functionality. The Healthbot can easily interact with other API’s, such as an Azure Function, which is connected to the Microsoft Graph that provides connections to Shifts in Teams.

The bot can request the needed information, enable triage and get the available slots from the Shifts application.

You can see it in action, with the Healthbot Author in the video below.

Azure Function

The Healthbot can securely connect to the Azure Function, which knows which Shifts agenda to query or update. The Function has 2 Methods, get the appointments and update an appointment

Get Open Appointments

To get the appointments, you need to write a little bit off code, which queries the Microsoft Graph en returns the available slots.

public static class GetOpenAppointmentsFunction
    {
        [FunctionName("GetOpenAppointments")]
        public static async Task<HttpResponseMessage> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            var theme = req.Query["theme"].ToString();
            var email = req.Query["email"].ToString();

            //blue : Citizin Appointment Vaccine
            //green: visiting family member	
            //purple: Employee vaccine appointment
            var teamId = "";
            string aadInstance = "https://login.microsoftonline.com/{0}";
            string tenant = "";
            string clientId = "f";
            string appKey = ""; // Or Client Secret
            string resourceId = "https://graph.microsoft.com/";
            string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);

            var authContext = new AuthenticationContext(authority);
            var clientCredential = new ClientCredential(clientId, appKey);
            var result = await authContext.AcquireTokenAsync(resourceId, clientCredential);
            string response;

            var availableShifts = new List<OpenShift>();

            using (HttpClient client = new HttpClient())
            {
                client.BaseAddress = new Uri("https://graph.microsoft.com");
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
                client.DefaultRequestHeaders
                    .Accept
                    .Add(new MediaTypeWithQualityHeaderValue("application/json"));
                client.DefaultRequestHeaders.Add("MS-APP-ACTS-AS", "");

                var resultApi = await client.GetAsync($"/v1.0/teams/{teamId}/schedule/openShifts?$top=1000");
                response = await resultApi.Content.ReadAsStringAsync();
                var shifts = JsonConvert.DeserializeObject<OpenShifts>(response);
                foreach (var shift in shifts.value)
                {
                    if (shift.draftOpenShift != null && shift.draftOpenShift.notes != null && shift.draftOpenShift.notes.Contains(email))
                    {
                        return new HttpResponseMessage(HttpStatusCode.Conflict)
                        {
                            Content = new StringContent(shift.draftOpenShift.startDateTime.Value.ToString("dd/MM/yyyy hH:mm"))
                        };
                    }

                    if (shift.draftOpenShift != null && shift.draftOpenShift.startDateTime < DateTime.Now.AddDays(20) && shift.draftOpenShift.startDateTime > DateTime.Now && shift.draftOpenShift.notes == null)
                    {
                        var activity = shift.draftOpenShift.activities.FirstOrDefault();
                        if (activity != null && activity.theme == theme)
                        {
                            availableShifts.Add(shift);
                        }
                    }
                }
            }

            return new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(JsonConvert.SerializeObject(availableShifts.OrderBy(p => p.draftOpenShift.startDateTime)), Encoding.UTF8, "application/json")
            };
        }
    }

Update Appointments

The code to update an appointment, is even more simple :).

public static class ReserveAppointmentFunction
    {
        [FunctionName("ReserveAppointment")]
        public static async Task<HttpResponseMessage> Run(
            [HttpTrigger(AuthorizationLevel.Function, "put", Route = null)] HttpRequest req,
            ILogger log)
        {
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            var shift = JsonConvert.DeserializeObject<OpenShift>(requestBody);
            var teamId = "";
            string aadInstance = "https://login.microsoftonline.com/{0}";
            string tenant = "";
            string clientId = "";
            string appKey = ""; // Or Client Secret
            string resourceId = "https://graph.microsoft.com/";
            string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);

            var authContext = new AuthenticationContext(authority);
            var clientCredential = new ClientCredential(clientId, appKey);
            var result = await authContext.AcquireTokenAsync(resourceId, clientCredential);
            string response;

            using (HttpClient client = new HttpClient())
            {
                client.BaseAddress = new Uri("https://graph.microsoft.com");
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

                client.DefaultRequestHeaders
                    .Accept
                    .Add(new MediaTypeWithQualityHeaderValue("application/json"));
                client.DefaultRequestHeaders.Add("MS-APP-ACTS-AS", "");
                string shiftSerialized = JsonConvert.SerializeObject(shift);
                using (HttpContent httpContent = new StringContent(shiftSerialized, Encoding.UTF8,"application/json"))
                {
                    var resultApi = await client.PutAsync($"/v1.0/teams/{teamId}/schedule/openShifts/{shift.id}", httpContent);
                    response = resultApi.StatusCode.ToString();
                }

                return new HttpResponseMessage(HttpStatusCode.OK)
                {
                    Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json")
                };
            }
            
        }

Shifts

You can create open shifts and add activities to provide different type of appointments. It is also possible to copy complete schedules for the coming weeks, which enables a scalable solution

It is also very easy to copy complete week schedules

Reporting

And because Shifts has a native PowerPlatform connector, you can easily create a Power App with the data in Shifts or from the data in the Healthbot.

By using existing blocks, it becomes very straightforward to create a secure and scalable self scheduling service.

Hope you like it 🙂
Bert!