March 28, 2019

Update user’s calendar in Field Service schedule board

Configuring the work hours for a user is straightforward from the Dynamics 365 UI, a couple of clicks and you are done. However in the last years we saw an increase of Field Service projects, hence the necessity to automatize the creation and the update of user's works hours.
C# code to update the calendar entity has been around from CRM 2011, like this one:
Sample code to update user’s calendar programmatically (work hours) in CRM 2011

The code linked above works perfectly when you see the work hours inside the user calendar, however they don't appear inside the Field Service schedule board. How can we make sure the work hours appear everywhere?

Nearly 50 lines of code to create a work hour entry are (at least to me) a bit excessive, but this is the way Dynamics work. I tried to simplify the code by renaming the variables and adding the code necessary for the work hours to appear inside the schedule board.
// we start with the user Id
Guid userId = Guid.Empty; // instead of Guid.Empty here we should have the real user Id

// we retrieve the calendarid from the user entity
Entity user = service.Retrieve("systemuser", userId, new ColumnSet("calendarid"));
Guid userCalendarId = user.GetAttributeValue("calendarid").Id;

// we retrieve the calendar record in order to get the Business Unit and the Calendar Rules
Entity userCalendar = service.Retrieve("calendar", userCalendarId , new ColumnSet("businessunitid"));
Guid calendarBusinessUnitRef = userCalendar.GetAttributeValue("businessunitid");
EntityCollection calendarRules = userCalendar.GetAttributeValue("calendarrules");

// we create a new calendar record (inner)
Entity innerCalendar = new Entity("calendar");
innerCalendar["businessunitid"] = calendarBusinessUnitRef;

// Field Service Schedule Board: we must define the type as Inner Calendar
// https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/types-calendars
innerCalendar["type"] = new OptionSetValue(-1);

Guid innerCalendarId = service.Create(innerCalendar);

// we create a calendar rule for the whole day we want to edit
Entity dayRule = new Entity("calendarrule");
dayRule["duration"] = 1440; // 24 hours in minutes
dayRule["effort"] = 1.0;
dayRule["extentcode"] = 1;
dayRule["pattern"] = "FREQ=DAILY;COUNT=1";
dayRule["rank"] = 0;
dayRule["timezonecode"] = 110; // 110 is (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
// GetAllTimeZonesWithDisplayNameRequest can be used to retrieve all the timezones with their code
dayRule["starttime"] = new DateTime(2019, 3, 28, 0, 0, 0, DateTimeKind.Utc);
dayRule["innercalendarid"] = new EntityReference("calendar", innerCalendarId);

// Field Service Schedule Board: we must also define the calendarid reference to the user calendar
dayRule["calendarid"] = new EntityReference("calendar", userCalendarId);

// we attach it to the Calendar Rules of the user
calendarRules.Entities.Add(dayRule);

// we update the user calendar to refresh the calendar rules collection
Entity updateUserCalendar = new Entity("calendar", userCalendarId);
updateUserCalendar["calendarrules"] = calendarRules;
service.Update(updateUserCalendar);

// we define the calendar rule containing our work hour 
Entity exactCalendarRule = new Entity("calendarrule");
exactCalendarRule["duration"] = 120; // 2 hours in minutes
exactCalendarRule["effort"] = 1.0;
exactCalendarRule["issimple"] = true;
exactCalendarRule["offset"] = 480; // 8 hours in minutes from start time (12:00)
exactCalendarRule["rank"] = 0;
exactCalendarRule["subcode"] = 1;
exactCalendarRule["timecode"] = 0;
exactCalendarRule["timezonecode"] = 110; // same timezone as the day rule
exactCalendarRule["calendarid"] = new EntityReference("calendar", innerCalendarId);

// we add the calendar rule to a collection
EntityCollection innerCalendarRules = new EntityCollection();
innerCalendarRules.EntityName = "calendarrule";
innerCalendarRules.Entities.Add(exactCalendarRule);

// we update the inner calendar with the new calendar rules 
innerCalendar["calendarrules"] = innerCalendarRules;
innerCalendar["calendarid"] = innerCalendarId;
service.Update(innerCalendar);

In the end to changes were necessary to make the work hour visible inside the Field Service schedule board:
  1. We must define the type for the Inner Calendar
  2. We must reference the user calendar inside the calendar rule the the whole day
I didn't discover this by myself but with the help of Microsoft support, hope it helps.