public async Task UpdateAvailabilityAsync(EmployeeAvailabilityModel availability) { Guard.ArgumentNotNullOrEmpty(availability.TeamsEmployeeId, nameof(EmployeeAvailabilityModel.TeamsEmployeeId)); var appClient = _clientFactory.CreateUserClient(_options, availability.TeamsEmployeeId); // get the existing shift preference from Teams var shiftPreference = await appClient.GetUserShiftPreferenceAsync(availability.TeamsEmployeeId).ConfigureAwait(false); shiftPreference.Availability = _availabilityMap.MapAvailability(availability); await appClient.UpdateUserShiftPreferenceAsync(shiftPreference, availability.TeamsEmployeeId).ConfigureAwait(false); }
public async Task DeleteAvailabilityAsync(EmployeeAvailabilityModel availability) { Guard.ArgumentNotNullOrEmpty(availability.TeamsEmployeeId, nameof(EmployeeAvailabilityModel.TeamsEmployeeId)); var appClient = _clientFactory.CreateUserClient(_options, availability.TeamsEmployeeId); // Teams does not support deleting of availability, so all we can do is to restore // availability to the default of all-day for all days of the week, however, we should // check to see if it is already default because if so, we should skip the operation // get the existing shift preference from Teams var shiftPreference = await appClient.GetUserShiftPreferenceAsync(availability.TeamsEmployeeId).ConfigureAwait(false); var defaultWeekAvailability = CreateDefaultAvailabilityForWeek(availability.TimeZoneInfoId); if (!AvailabilityMatches(shiftPreference.Availability, defaultWeekAvailability)) { shiftPreference.Availability = defaultWeekAvailability; await appClient.UpdateUserShiftPreferenceAsync(shiftPreference, availability.TeamsEmployeeId).ConfigureAwait(false); } }
/// <summary> /// Maps Teams availability items to the internal employee availability model representation. /// </summary> /// <param name="availabilityItems">The list of Teams availability items</param> /// <param name="userId">The Teams user ID</param> /// <returns>The internal model.</returns> public EmployeeAvailabilityModel MapAvailability(IList <AvailabilityItem> availabilityItems, string userId) { var availabilityModel = new EmployeeAvailabilityModel { TeamsEmployeeId = userId, Availability = new List <AvailabilityModel>(), StartDate = _timeService.UtcNow.StartOfWeek(_options.StartDayOfWeek), // in Teams shift preferences have no end date, they continue indefinitely until changed EndDate = null, // Teams only has single week availability NumberOfWeeks = 1, TimeZoneInfoId = availabilityItems.Count > 0 ? availabilityItems.First().TimeZone : null }; foreach (var availabilityItem in availabilityItems) { foreach (var dayOfWeek in availabilityItem.Recurrence.Pattern.DaysOfWeek) { if (availabilityItem.TimeSlots != null) { foreach (var timeslot in availabilityItem.TimeSlots) { availabilityModel.Availability.Add(new AvailabilityModel { DayOfWeek = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), CultureInfo.CurrentCulture.TextInfo.ToTitleCase(dayOfWeek)), // convert times from whatever time zone Teams sends them to us to UTC StartTime = DateTimeHelper.ConvertFromLocalTime(timeslot.StartTime, availabilityItem.TimeZone, _timeService), EndTime = DateTimeHelper.ConvertFromLocalTime(timeslot.EndTime, availabilityItem.TimeZone, _timeService), // as Teams only supports a single availability week, default it WeekNumber = 1 }); } } } } return(availabilityModel); }
/// <summary> /// Maps the internal employee availability model back to Teams availability items. /// </summary> /// <param name="availabilityModel">The model to map.</param> /// <param name="timeZoneInfoId">The time zone id to use to convert utc back to local.</param> /// <returns>The list of Teams availability items.</returns> public IList <AvailabilityItem> MapAvailability(EmployeeAvailabilityModel availabilityModel) { // when writing availability to Teams, we need to use IANA time zones rather than // Windows, so convert if necessary if (!TZConvert.TryWindowsToIana(availabilityModel.TimeZoneInfoId, out string ianaTimeZone)) { ianaTimeZone = availabilityModel.TimeZoneInfoId; } // Some WFM providers allow for availability to be different for different weeks on an // alternating basis, however, Teams only allows a single 'current' availability pattern // to be defined per user this means that we need to identify the item in the rotational // availability that represents the current week. var rotationalWeekNumber = (((int)((_timeService.UtcNow - availabilityModel.CycleBaseDate).TotalDays / 7)) % availabilityModel.NumberOfWeeks) + 1; var groupedByDayItems = availabilityModel.Availability .Where(a => a.WeekNumber == rotationalWeekNumber) .GroupBy(a => a.DayOfWeek.ToString(), a => new TimeSlotItem { StartTime = a.StartTime, EndTime = a.EndTime }, (key, g) => new { Day = key, TimeSlots = g.ToList() }); var availabilityItems = new List <AvailabilityItem>(); foreach (var item in groupedByDayItems) { var availabilityItem = new AvailabilityItem { Recurrence = new RecurrenceItem { Pattern = new PatternItem { DaysOfWeek = new List <string> { item.Day }, Interval = 1, Type = "Weekly" }, Range = new RangeItem { Type = "noEnd" } }, TimeSlots = new List <TimeSlotItem>(), TimeZone = ianaTimeZone }; foreach (var timeSlot in item.TimeSlots) { availabilityItem.TimeSlots.Add(new TimeSlotItem { StartTime = DateTimeHelper.ConvertToLocalTime(timeSlot.StartTime, availabilityModel.TimeZoneInfoId, _timeService), EndTime = DateTimeHelper.ConvertToLocalTime(timeSlot.EndTime, availabilityModel.TimeZoneInfoId, _timeService) }); } availabilityItems.Add(availabilityItem); } if (availabilityItems.Count < 7) { AddUnavailableDays(availabilityItems, ianaTimeZone); } return(availabilityItems); }
public static void LogAvailabilitySkipped(this ILogger log, string teamId, string operationName, EmployeeAvailabilityModel availability) { log.LogTrace(EventIds.Availability, "Availability: Status={status}, OperationName={operationName}, SourceId={sourceId}, EmployeeId={employeeId}, StartDate={startDate}, EndDate={endDate}, TeamId={teamId}", Status.Skipped, operationName, availability.WfmId, availability.WfmEmployeeId, availability.StartDate.AsDateString(), availability.EndDate.AsDateString(), teamId); }
public static void LogAvailabilityError(this ILogger log, MicrosoftGraphException ex, string teamId, string operationName, EmployeeAvailabilityModel availability) { log.LogError(EventIds.Availability, ex, "Availability: Status={status}, OperationName={operationName}, ErrorCode={errorCode}, ErrorDescription={errorDescription}, ErrorRequestId={errorRequestId}, ErrorDate={errorDate}, SourceId={sourceId}, EmployeeId={employeeId}, StartDate={startDate}, EndDate={endDate}, TeamId={teamId}", Status.Failed, operationName, ex.Error.Code, ex.Error.Message, ex.Error.InnerError?.RequestId, ex.Error.InnerError?.Date, availability.WfmId, availability.WfmEmployeeId, availability.StartDate.AsDateString(), availability.EndDate.AsDateString(), teamId); }