/// <summary>
        /// Method that approves a time off request.
        /// </summary>
        /// <param name="timeOffLookUpEntriesFoundList">The time off look up entries that are found.</param>
        /// <param name="user">The user.</param>
        /// <param name="configurationDetails">The configuration details.</param>
        /// <param name="monthPartitionKey">The monthwise partition key.</param>
        /// <param name="globalTimeOffRequestDetails">The list of global time off request details.</param>
        /// <returns>A unit of execution.</returns>
        private async Task ApproveTimeOffRequestAsync(
            List <TimeOffMappingEntity> timeOffLookUpEntriesFoundList,
            List <UserDetailsModel> user,
            SetupDetails configurationDetails,
            string monthPartitionKey,
            List <GlobalTimeOffRequestItem> globalTimeOffRequestDetails)
        {
            this.telemetryClient.TrackTrace($"ApproveTimeOffRequestAsync started for {monthPartitionKey}.");

            for (int i = 0; i < timeOffLookUpEntriesFoundList.Count; i++)
            {
                var timeOffReqCon = new
                {
                    Message = string.Empty,
                };

                var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI");

                // Send Passthrough header to indicate the sender of request in outbound call.
                httpClient.DefaultRequestHeaders.Add("X-MS-WFMPassthrough", configurationDetails.WFIId);

                var requestUrl    = $"teams/{user[i].ShiftTeamId}/schedule/timeOffRequests/{timeOffLookUpEntriesFoundList[i].ShiftsRequestId}/approve";
                var requestString = JsonConvert.SerializeObject(timeOffReqCon);

                var response = await this.graphUtility.SendHttpRequest(configurationDetails.GraphConfigurationDetails, httpClient, HttpMethod.Post, requestUrl, requestString).ConfigureAwait(false);

                if (response.IsSuccessStatusCode)
                {
                    TimeOffMappingEntity timeOffMappingEntity = new TimeOffMappingEntity
                    {
                        Duration           = globalTimeOffRequestDetails[i].TimeOffPeriods.TimeOffPeriod.Duration,
                        EndDate            = globalTimeOffRequestDetails[i].TimeOffPeriods.TimeOffPeriod.EndDate,
                        StartDate          = globalTimeOffRequestDetails[i].TimeOffPeriods.TimeOffPeriod.StartDate,
                        StartTime          = globalTimeOffRequestDetails[i].TimeOffPeriods.TimeOffPeriod.StartTime,
                        PayCodeName        = globalTimeOffRequestDetails[i].TimeOffPeriods.TimeOffPeriod.PayCodeName,
                        KronosPersonNumber = globalTimeOffRequestDetails[i].Employee.PersonIdentity.PersonNumber,
                        PartitionKey       = monthPartitionKey,
                        RowKey             = globalTimeOffRequestDetails[i].Id,
                        ShiftsRequestId    = timeOffLookUpEntriesFoundList[i].ShiftsRequestId,
                        IsActive           = true,
                        KronosRequestId    = globalTimeOffRequestDetails[i].Id,
                        ShiftsStatus       = ApiConstants.ApprovedStatus,
                        KronosStatus       = ApiConstants.ApprovedStatus,
                    };

                    this.AddorUpdateTimeOffMappingAsync(timeOffMappingEntity);
                }
            }

            this.telemetryClient.TrackTrace($"ApproveTimeOffRequestAsync ended for {monthPartitionKey}.");
        }
        /// <summary>
        /// Cancels a time off request that was  in Teams.
        /// </summary>
        /// <param name="timeOffRequestMapping">The mapping for the time off request.</param>
        /// <returns>Whether the time off request was cancelled successfully or not.</returns>
        internal async Task <bool> CancelTimeOffRequestInKronosAsync(TimeOffMappingEntity timeOffRequestMapping)
        {
            var timeOffRequestQueryDateSpan = $"{timeOffRequestMapping.StartDate}-{timeOffRequestMapping.EndDate}";

            // Get all the necessary prerequisites.
            var allRequiredConfigurations = await this.utility.GetAllConfigurationsAsync().ConfigureAwait(false);

            var kronosUserId    = timeOffRequestMapping.KronosPersonNumber;
            var kronosRequestId = timeOffRequestMapping.KronosRequestId;

            Dictionary <string, string> data = new Dictionary <string, string>
            {
                { "KronosPersonNumber", $"{kronosUserId}" },
                { "KronosTimeOffRequestId", $"{kronosRequestId}" },
                { "Configured correctly", $"{allRequiredConfigurations.IsAllSetUpExists}" },
                { "Date range", $"{timeOffRequestQueryDateSpan}" },
            };

            if (allRequiredConfigurations.IsAllSetUpExists)
            {
                var response =
                    await this.timeOffActivity.CancelTimeOffRequestAsync(
                        new Uri(allRequiredConfigurations.WfmEndPoint),
                        allRequiredConfigurations.KronosSession,
                        timeOffRequestQueryDateSpan,
                        kronosUserId,
                        kronosRequestId).ConfigureAwait(false);

                data.Add("ResponseStatus", $"{response.Status}");

                if (response.Status == "Success")
                {
                    this.telemetryClient.TrackTrace($"Update table for cancellation of time off request: {kronosRequestId}", data);
                    timeOffRequestMapping.KronosStatus = ApiConstants.Retracted;
                    timeOffRequestMapping.ShiftsStatus = ApiConstants.Retracted;
                    await this.timeOffMappingEntityProvider.SaveOrUpdateTimeOffMappingEntityAsync(timeOffRequestMapping).ConfigureAwait(false);

                    return(true);
                }
            }

            this.telemetryClient.TrackTrace("CancelTimeOffRequestInKronos Failed", data);
            return(false);
        }
Exemple #3
0
        /// <summary>
        /// Method to decline the time off request.
        /// </summary>
        /// <param name="globalTimeOffRequestItem">The time off request item.</param>
        /// <param name="user">The user.</param>
        /// <param name="timeOffId">The time off Id from Kronos.</param>
        /// <param name="accessToken">The MS Graph Access token.</param>
        /// <param name="monthPartitionKey">The month wise partition key.</param>
        /// <returns>A unit of execution.</returns>
        private async Task DeclineTimeOffRequestAsync(
            GlobalTimeOffRequestItem globalTimeOffRequestItem,
            UserDetailsModel user,
            string timeOffId,
            string accessToken,
            string monthPartitionKey)
        {
            this.telemetryClient.TrackTrace($"DeclineTimeOffRequestAsync started for time off id {timeOffId}.");
            var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI");

            httpClient.DefaultRequestHeaders.Authorization =
                new AuthenticationHeaderValue("Bearer", accessToken);
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "teams/" + user.ShiftTeamId + "/schedule/timeOffRequests/" + timeOffId + "/decline"))
            {
                var response = await httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false);

                if (response.IsSuccessStatusCode)
                {
                    var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                    TimeOffMappingEntity timeOffMappingEntity = new TimeOffMappingEntity
                    {
                        Duration           = globalTimeOffRequestItem.TimeOffPeriods.TimeOffPeriod.Duration,
                        EndDate            = globalTimeOffRequestItem.TimeOffPeriods.TimeOffPeriod.EndDate,
                        StartDate          = globalTimeOffRequestItem.TimeOffPeriods.TimeOffPeriod.StartDate,
                        StartTime          = globalTimeOffRequestItem.TimeOffPeriods.TimeOffPeriod.StartTime,
                        PayCodeName        = globalTimeOffRequestItem.TimeOffPeriods.TimeOffPeriod.PayCodeName,
                        KronosPersonNumber = globalTimeOffRequestItem.Employee.PersonIdentity.PersonNumber,
                        PartitionKey       = monthPartitionKey,
                        RowKey             = globalTimeOffRequestItem.Id,
                        ShiftsRequestId    = timeOffId,
                        IsActive           = true,
                        KronosRequestId    = globalTimeOffRequestItem.Id,
                        StatusName         = globalTimeOffRequestItem.StatusName,
                    };

                    this.AddorUpdateTimeOffMappingAsync(timeOffMappingEntity);
                }
            }

            this.telemetryClient.TrackTrace($"DeclineTimeOffRequestAsync ended for time off id {timeOffId}.");
        }
        /// <summary>
        /// Method to decline the time off request.
        /// </summary>
        /// <param name="globalTimeOffRequestItem">The time off request item.</param>
        /// <param name="user">The user.</param>
        /// <param name="timeOffId">The time off Id from Kronos.</param>
        /// <param name="configurationDetails">The configuration details.</param>
        /// <param name="monthPartitionKey">The month wise partition key.</param>
        /// <returns>A unit of execution.</returns>
        private async Task DeclineTimeOffRequestAsync(
            GlobalTimeOffRequestItem globalTimeOffRequestItem,
            UserDetailsModel user,
            string timeOffId,
            SetupDetails configurationDetails,
            string monthPartitionKey)
        {
            this.telemetryClient.TrackTrace($"DeclineTimeOffRequestAsync started for time off id {timeOffId}.");

            var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI");

            // Send Passthrough header to indicate the sender of request in outbound call.
            httpClient.DefaultRequestHeaders.Add("X-MS-WFMPassthrough", configurationDetails.WFIId);

            var requestUrl = $"teams/" + user.ShiftTeamId + "/schedule/timeOffRequests/" + timeOffId + "/decline";

            var response = await this.graphUtility.SendHttpRequest(configurationDetails.GraphConfigurationDetails, httpClient, HttpMethod.Post, requestUrl).ConfigureAwait(false);

            if (response.IsSuccessStatusCode)
            {
                TimeOffMappingEntity timeOffMappingEntity = new TimeOffMappingEntity
                {
                    Duration           = globalTimeOffRequestItem.TimeOffPeriods.TimeOffPeriod.Duration,
                    EndDate            = globalTimeOffRequestItem.TimeOffPeriods.TimeOffPeriod.EndDate,
                    StartDate          = globalTimeOffRequestItem.TimeOffPeriods.TimeOffPeriod.StartDate,
                    StartTime          = globalTimeOffRequestItem.TimeOffPeriods.TimeOffPeriod.StartTime,
                    PayCodeName        = globalTimeOffRequestItem.TimeOffPeriods.TimeOffPeriod.PayCodeName,
                    KronosPersonNumber = globalTimeOffRequestItem.Employee.PersonIdentity.PersonNumber,
                    PartitionKey       = monthPartitionKey,
                    RowKey             = globalTimeOffRequestItem.Id,
                    ShiftsRequestId    = timeOffId,
                    IsActive           = true,
                    KronosRequestId    = globalTimeOffRequestItem.Id,
                    ShiftsStatus       = globalTimeOffRequestItem.StatusName,
                    KronosStatus       = ApiConstants.Refused,
                };

                this.AddorUpdateTimeOffMappingAsync(timeOffMappingEntity);
            }

            this.telemetryClient.TrackTrace($"DeclineTimeOffRequestAsync ended for time off id {timeOffId}.");
        }
Exemple #5
0
        private async Task <TableResult> StoreOrUpdateEntityAsync(TimeOffMappingEntity entity)
        {
            if (entity is null)
            {
                throw new ArgumentNullException(nameof(entity));
            }

            await this.EnsureInitializedAsync().ConfigureAwait(false);

            var storeOrUpdateEntityProps = new Dictionary <string, string>()
            {
                { "CallingAssembly", Assembly.GetCallingAssembly().GetName().Name },
            };

            this.telemetryClient.TrackEvent("StoreOrUpdateEntityAsync", storeOrUpdateEntityProps);

            TableOperation addOrUpdateOperation = TableOperation.InsertOrReplace(entity);

            return(await this.timeoffEntityMappingTable.ExecuteAsync(addOrUpdateOperation).ConfigureAwait(false));
        }
Exemple #6
0
        /// <summary>
        /// Saves or updates an time off mapping to Azure table storage.
        /// </summary>
        /// <param name="entity">The time off mapping entity.</param>
        /// <returns>A unit of execution.</returns>
        public Task SaveOrUpdateTimeOffMappingEntityAsync(
            TimeOffMappingEntity entity)
        {
            if (entity is null)
            {
                throw new ArgumentNullException(nameof(entity));
            }

            var saveOrUpdateTimeOffMappingProps = new Dictionary <string, string>()
            {
                { "KronosRequestId", entity?.KronosRequestId },
                { "ShiftsRequestId", entity?.ShiftsRequestId },
                { "CallingAssembly", Assembly.GetCallingAssembly().GetName().Name },
            };

            this.telemetryClient.TrackTrace(
                MethodBase.GetCurrentMethod().Name,
                saveOrUpdateTimeOffMappingProps);

            return(this.StoreOrUpdateEntityAsync(entity));
        }
Exemple #7
0
 /// <summary>
 /// The method which will add or update a Time Off Mapping in Azure table storage.
 /// </summary>
 /// <param name="timeOffMappingEntity">The time off mapping entity to update or add.</param>
 private async void AddorUpdateTimeOffMappingAsync(TimeOffMappingEntity timeOffMappingEntity)
 {
     await this.azureTableStorageHelper.InsertOrMergeTableEntityAsync(timeOffMappingEntity, "TimeOffMapping").ConfigureAwait(false);
 }
Exemple #8
0
        /// <summary>
        /// Method that approves a time off request.
        /// </summary>
        /// <param name="timeOffLookUpEntriesFoundList">The time off look up entries that are found.</param>
        /// <param name="user">The user.</param>
        /// <param name="timeOffReasonId">The Shifts Time Off Reason ID.</param>
        /// <param name="accessToken">The MS Graph Access Token.</param>
        /// <param name="monthPartitionKey">The monthwise partition key.</param>
        /// <param name="globalTimeOffRequestDetails">The list of global time off request details.</param>
        /// <returns>A unit of execution.</returns>
        private async Task ApproveTimeOffRequestAsync(
            List <TimeOffMappingEntity> timeOffLookUpEntriesFoundList,
            List <UserDetailsModel> user,
            List <PayCodeToTimeOffReasonsMappingEntity> timeOffReasonId,
            string accessToken,
            string monthPartitionKey,
            List <GlobalTimeOffRequestItem> globalTimeOffRequestDetails)
        {
            this.telemetryClient.TrackTrace($"ApproveTimeOffRequestAsync started for {monthPartitionKey}.");

            for (int i = 0; i < timeOffLookUpEntriesFoundList.Count; i++)
            {
                var timeOffReqCon = new TimeOffRequestItem
                {
                    Id = timeOffLookUpEntriesFoundList[i].ShiftsRequestId,
                    CreatedDateTime      = DateTime.Now,
                    LastModifiedDateTime = DateTime.Now,
                    AssignedTo           = ApiConstants.Manager,
                    State                 = ApiConstants.Pending,
                    SenderDateTime        = DateTime.Now,
                    SenderMessage         = globalTimeOffRequestDetails[i].Comments?.Comment.FirstOrDefault()?.CommentText,
                    SenderUserId          = Guid.Parse(user[i].ShiftUserId),
                    ManagerActionDateTime = null,
                    ManagerActionMessage  = null,
                    ManagerUserId         = string.Empty,
                    StartDateTime         = this.utility.CalculateStartDateTime(globalTimeOffRequestDetails[i]),
                    EndDateTime           = this.CalculateEndDate(globalTimeOffRequestDetails[i]),
                    TimeOffReasonId       = timeOffReasonId[i].TimeOffReasonId,
                    LastModifiedBy        = new LastModifiedBy
                    {
                        Application  = null,
                        Device       = null,
                        Conversation = null,
                        User         = new TimeOffRequest.User
                        {
                            Id          = Guid.Parse(user[i].ShiftUserId),
                            DisplayName = user[i].ShiftUserDisplayName,
                        },
                    },
                };

                var requestString = JsonConvert.SerializeObject(timeOffReqCon);
                var httpClient    = this.httpClientFactory.CreateClient("ShiftsAPI");
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "teams/" + user[i].ShiftTeamId + "/schedule/timeOffRequests/" + timeOffLookUpEntriesFoundList[i].ShiftsRequestId + "/approve")
                {
                    Content = new StringContent(requestString, Encoding.UTF8, "application/json"),
                })
                {
                    var response = await httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false);

                    if (response.IsSuccessStatusCode)
                    {
                        var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                        TimeOffMappingEntity timeOffMappingEntity = new TimeOffMappingEntity
                        {
                            Duration           = globalTimeOffRequestDetails[i].TimeOffPeriods.TimeOffPeriod.Duration,
                            EndDate            = globalTimeOffRequestDetails[i].TimeOffPeriods.TimeOffPeriod.EndDate,
                            StartDate          = globalTimeOffRequestDetails[i].TimeOffPeriods.TimeOffPeriod.StartDate,
                            StartTime          = globalTimeOffRequestDetails[i].TimeOffPeriods.TimeOffPeriod.StartTime,
                            PayCodeName        = globalTimeOffRequestDetails[i].TimeOffPeriods.TimeOffPeriod.PayCodeName,
                            KronosPersonNumber = globalTimeOffRequestDetails[i].Employee.PersonIdentity.PersonNumber,
                            PartitionKey       = monthPartitionKey,
                            RowKey             = globalTimeOffRequestDetails[i].Id,
                            ShiftsRequestId    = timeOffLookUpEntriesFoundList[i].ShiftsRequestId,
                            IsActive           = true,
                            KronosRequestId    = globalTimeOffRequestDetails[i].Id,
                            StatusName         = ApiConstants.ApprovedStatus,
                        };

                        this.AddorUpdateTimeOffMappingAsync(timeOffMappingEntity);
                    }
                }
            }

            this.telemetryClient.TrackTrace($"ApproveTimeOffRequestAsync ended for {monthPartitionKey}.");
        }
Exemple #9
0
        /// <summary>
        /// Method that will add a time off request.
        /// </summary>
        /// <param name="graphClient">The MS Graph Client.</param>
        /// <param name="userModelNotFoundList">The list of users that are not found.</param>
        /// <param name="timeOffNotFoundList">This list of time off records that are not found.</param>
        /// <param name="kronosPayCodeList">The list of Kronos WFC Paycodes.</param>
        /// <param name="monthPartitionKey">The month partition key.</param>
        /// <returns>A unit of execution.</returns>
        private async Task AddTimeOffRequestAsync(
            GraphServiceClient graphClient,
            List <UserDetailsModel> userModelNotFoundList,
            List <GlobalTimeOffRequestItem> timeOffNotFoundList,
            List <PayCodeToTimeOffReasonsMappingEntity> kronosPayCodeList,
            string monthPartitionKey)
        {
            var telemetryProps = new Dictionary <string, string>()
            {
                { "CallingAssembly", Assembly.GetCallingAssembly().GetName().Name },
            };

            this.telemetryClient.TrackTrace($"AddTimeOffRequestAsync started for {monthPartitionKey}.", telemetryProps);

            // create entries from not found list
            for (int i = 0; i < timeOffNotFoundList.Count && kronosPayCodeList?.Count > 0; i++)
            {
                if (kronosPayCodeList[i]?.TimeOffReasonId != null)
                {
                    var timeOff = new TimeOff
                    {
                        UserId        = userModelNotFoundList[i].ShiftUserId,
                        SharedTimeOff = new TimeOffItem
                        {
                            TimeOffReasonId = kronosPayCodeList[i].TimeOffReasonId,
                            StartDateTime   = this.utility.CalculateStartDateTime(timeOffNotFoundList[i]),
                            EndDateTime     = this.CalculateEndDate(timeOffNotFoundList[i]),
                            Theme           = ScheduleEntityTheme.White,
                        },
                    };

                    var timeOffs = await graphClient.Teams[userModelNotFoundList[i].ShiftTeamId].Schedule.TimesOff
                                   .Request()
                                   .AddAsync(timeOff).ConfigureAwait(false);

                    TimeOffMappingEntity timeOffMappingEntity = new TimeOffMappingEntity
                    {
                        Duration           = timeOffNotFoundList[i].TimeOffPeriods.TimeOffPeriod.Duration,
                        EndDate            = timeOffNotFoundList[i].TimeOffPeriods.TimeOffPeriod.EndDate,
                        StartDate          = timeOffNotFoundList[i].TimeOffPeriods.TimeOffPeriod.StartDate,
                        StartTime          = timeOffNotFoundList[i].TimeOffPeriods.TimeOffPeriod.StartTime,
                        PayCodeName        = timeOffNotFoundList[i].TimeOffPeriods.TimeOffPeriod.PayCodeName,
                        KronosPersonNumber = timeOffNotFoundList[i].Employee.PersonIdentity.PersonNumber,
                        PartitionKey       = monthPartitionKey,
                        RowKey             = timeOffNotFoundList[i].Id,
                        ShiftsRequestId    = timeOffs.Id,
                        KronosRequestId    = timeOffNotFoundList[i].Id,
                        StatusName         = timeOffNotFoundList[i].StatusName,
                        IsActive           = true,
                    };

                    this.AddorUpdateTimeOffMappingAsync(timeOffMappingEntity);
                }
                else
                {
                    telemetryProps.Add("TimeOffReason for " + timeOffNotFoundList[i].Id, "NotFound");
                }

                this.telemetryClient.TrackTrace($"AddTimeOffRequestAsync ended for {monthPartitionKey}.", telemetryProps);
            }
        }
Exemple #10
0
        /// <summary>
        /// start timeoffrequests sync from Kronos and push it to Shifts.
        /// </summary>
        /// <param name="isRequestFromLogicApp">Checks if request is coming from logic app or portal.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        internal async Task ProcessTimeOffRequestsAsync(string isRequestFromLogicApp)
        {
            this.telemetryClient.TrackTrace($"{Resource.ProcessTimeOffRequetsAsync} starts at: {DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)} for isRequestFromLogicApp: " + isRequestFromLogicApp);

            this.utility.SetQuerySpan(Convert.ToBoolean(isRequestFromLogicApp, CultureInfo.InvariantCulture), out string timeOffStartDate, out string timeOffEndDate);

            var allRequiredConfigurations = await this.utility.GetAllConfigurationsAsync().ConfigureAwait(false);

            // Check whether date range are in correct format.
            var isCorrectDateRange = Utility.CheckDates(timeOffStartDate, timeOffEndDate);

            if (allRequiredConfigurations != null && (bool)allRequiredConfigurations?.IsAllSetUpExists && isCorrectDateRange)
            {
                TimeOffRequestResponse.TimeOffRequestRes timeOffRequestContent = default(TimeOffRequestResponse.TimeOffRequestRes);

                // Get the mapped user details from user to user mapping table.
                var allUsers = await UsersHelper.GetAllMappedUserDetailsAsync(allRequiredConfigurations.WFIId, this.userMappingProvider, this.teamDepartmentMappingProvider, this.telemetryClient).ConfigureAwait(false);

                // Get distinct Teams.
                var allteamDetails = allUsers?.Select(x => x.ShiftTeamId).Distinct().ToList();

                // Get list of time off reasons from pay code to time off reason mapping table
                var timeOffReasons = await this.timeOffReasonProvider.GetTimeOffReasonsAsync().ConfigureAwait(false);

                var monthPartitions = Utility.GetMonthPartition(timeOffStartDate, timeOffEndDate);

                List <TimeOffRequestResponse.TimeOffRequestItem> timeOffRequestItems = new List <TimeOffRequestResponse.TimeOffRequestItem>();
                bool hasMoreTimeOffs = false;

                if (monthPartitions != null && monthPartitions.Count > 0)
                {
                    foreach (var monthPartitionKey in monthPartitions)
                    {
                        timeOffRequestItems.Clear();
                        hasMoreTimeOffs = false;
                        string queryStartDate, queryEndDate;
                        Utility.GetNextDateSpan(
                            monthPartitionKey,
                            monthPartitions.FirstOrDefault(),
                            monthPartitions.LastOrDefault(),
                            timeOffStartDate,
                            timeOffEndDate,
                            out queryStartDate,
                            out queryEndDate);

                        foreach (var team in allteamDetails)
                        {
                            timeOffRequestItems.Clear();
                            hasMoreTimeOffs = false;
                            var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI");
                            Uri requestUri = new Uri(this.appSettings.GraphApiUrl + "teams/" + team + "/schedule/timeoffrequests?$filter=state eq 'pending'");
                            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", allRequiredConfigurations.ShiftsAccessToken);
                            do
                            {
                                using (var httpRequestMessage = new HttpRequestMessage()
                                {
                                    Method = HttpMethod.Get,
                                    RequestUri = requestUri,
                                })
                                {
                                    var response = await httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false);

                                    if (response.IsSuccessStatusCode)
                                    {
                                        timeOffRequestContent = await response.Content.ReadAsAsync <TimeOffRequestResponse.TimeOffRequestRes>().ConfigureAwait(false);

                                        timeOffRequestItems.AddRange(timeOffRequestContent.TORItem);
                                        if (timeOffRequestContent.NextLink != null)
                                        {
                                            hasMoreTimeOffs = true;
                                            requestUri      = timeOffRequestContent.NextLink;
                                        }
                                        else
                                        {
                                            hasMoreTimeOffs = false;
                                        }
                                    }
                                    else
                                    {
                                        this.telemetryClient.TrackTrace("SyncTimeOffRequestsFromShiftsToKronos - " + response.StatusCode.ToString());
                                    }
                                }
                            }while (hasMoreTimeOffs);

                            if (timeOffRequestItems?.Count > 0)
                            {
                                // get the team mappings for the team and pick the first because we need the Kronos Time Zone
                                var mappedTeams = await this.teamDepartmentMappingProvider.GetMappedTeamDetailsAsync(team).ConfigureAwait(false);

                                var mappedTeam     = mappedTeams.FirstOrDefault();
                                var kronosTimeZone = string.IsNullOrEmpty(mappedTeam?.KronosTimeZone) ? this.appSettings.KronosTimeZone : mappedTeam.KronosTimeZone;

                                foreach (var item in timeOffRequestItems)
                                {
                                    var timeOffReqStartDate = this.utility.UTCToKronosTimeZone(item.StartDateTime, kronosTimeZone);
                                    if (timeOffReqStartDate < DateTime.ParseExact(queryStartDate, Common.Constants.DateFormat, CultureInfo.InvariantCulture) ||
                                        timeOffReqStartDate > DateTime.ParseExact(queryEndDate, Common.Constants.DateFormat, CultureInfo.InvariantCulture).AddDays(1))
                                    {
                                        continue;
                                    }

                                    List <TimeOffMappingEntity> timeOffMappingEntity = await this.timeOffReqMappingEntityProvider.GetAllTimeOffReqMappingEntitiesAsync(
                                        monthPartitionKey,
                                        item.Id).ConfigureAwait(false);

                                    if (timeOffMappingEntity.Count == 0)
                                    {
                                        var timeOffReason = timeOffReasons.Find(t => t.TimeOffReasonId == item.TimeOffReasonId);

                                        var personDetails = allUsers.FirstOrDefault(u => u.ShiftUserId == Convert.ToString(item.SenderUserId, CultureInfo.InvariantCulture));

                                        // Get the Kronos WFC API Time Zone Info
                                        var kronosTimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(kronosTimeZone);

                                        // Create the Kronos Time Off Request.
                                        var timeOffResponse = await this.createTimeOffActivity.TimeOffRequestAsync(
                                            allRequiredConfigurations.KronosSession,
                                            item.StartDateTime,
                                            item.EndDateTime,
                                            personDetails?.KronosPersonNumber,
                                            timeOffReason?.RowKey,
                                            new Uri(allRequiredConfigurations.WfmEndPoint),
                                            kronosTimeZoneInfo).ConfigureAwait(false);

                                        // If there is an error from Kronos side.
                                        if (string.IsNullOrWhiteSpace(timeOffResponse?.Error?.Message))
                                        {
                                            var submitTimeOffResponse = await this.createTimeOffActivity.SubmitTimeOffRequestAsync(
                                                allRequiredConfigurations.KronosSession,
                                                personDetails.KronosPersonNumber,
                                                timeOffResponse?.EmployeeRequestMgm?.RequestItem?.GlobalTimeOffRequestItms?.FirstOrDefault()?.Id,
                                                queryStartDate,
                                                queryEndDate,
                                                new Uri(allRequiredConfigurations.WfmEndPoint)).ConfigureAwait(false);

                                            TimeOffMappingEntity newTimeOffReq = new TimeOffMappingEntity();
                                            if (submitTimeOffResponse?.Status == ApiConstants.Failure)
                                            {
                                                newTimeOffReq.IsActive = false;
                                            }
                                            else
                                            {
                                                newTimeOffReq.IsActive = true;
                                            }

                                            newTimeOffReq.Duration           = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().TimeOffPeriodsList.TimeOffPerd.FirstOrDefault().Duration;
                                            newTimeOffReq.EndDate            = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().TimeOffPeriodsList.TimeOffPerd.FirstOrDefault().EndDate;
                                            newTimeOffReq.StartDate          = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().TimeOffPeriodsList.TimeOffPerd.FirstOrDefault().StartDate;
                                            newTimeOffReq.StartTime          = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().TimeOffPeriodsList.TimeOffPerd.FirstOrDefault().StartTime;
                                            newTimeOffReq.PayCodeName        = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().TimeOffPeriodsList.TimeOffPerd.FirstOrDefault().PayCodeName;
                                            newTimeOffReq.KronosPersonNumber = timeOffResponse.EmployeeRequestMgm.Employees.PersonIdentity.PersonNumber;
                                            newTimeOffReq.PartitionKey       = monthPartitionKey;
                                            newTimeOffReq.RowKey             = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().Id;
                                            newTimeOffReq.ShiftsRequestId    = item.Id;
                                            newTimeOffReq.KronosRequestId    = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().Id;
                                            newTimeOffReq.StatusName         = ApiConstants.SubmitRequests;

                                            this.AddorUpdateTimeOffMappingAsync(newTimeOffReq);
                                        }
                                        else
                                        {
                                            this.telemetryClient.TrackTrace(timeOffResponse.Error.DetailErrors.Error[0].Message);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                else
                {
                    this.telemetryClient.TrackTrace("SyncTimeOffRequestsFromShifsToKronos - " + Resource.NullMonthPartitionsMessage);
                }
            }
            else
            {
                this.telemetryClient.TrackTrace("SyncTimeOffRequestsFromShiftsToKronos - " + Resource.SetUpNotDoneMessage);
            }

            this.telemetryClient.TrackTrace($"{Resource.ProcessTimeOffRequetsAsync} ended at: {DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)}");
        }
        /// <summary>
        /// Method that will add a time off request.
        /// </summary>
        /// <param name="configurationDetails">The configuration details.</param>
        /// <param name="userModelNotFoundList">The list of users that are not found.</param>
        /// <param name="timeOffNotFoundList">This list of time off records that are not found.</param>
        /// <param name="kronosPayCodeList">The list of Kronos WFC Paycodes.</param>
        /// <param name="monthPartitionKey">The month partition key.</param>
        /// <returns>A unit of execution.</returns>
        private async Task AddTimeOffAsync(
            SetupDetails configurationDetails,
            List <UserDetailsModel> userModelNotFoundList,
            List <GlobalTimeOffRequestItem> timeOffNotFoundList,
            List <PayCodeToTimeOffReasonsMappingEntity> kronosPayCodeList,
            string monthPartitionKey)
        {
            var telemetryProps = new Dictionary <string, string>()
            {
                { "CallingAssembly", Assembly.GetCallingAssembly().GetName().Name },
            };

            this.telemetryClient.TrackTrace($"AddTimeOffRequestAsync started for {monthPartitionKey}.", telemetryProps);

            // create entries from not found list
            for (int i = 0; i < timeOffNotFoundList.Count && kronosPayCodeList?.Count > 0; i++)
            {
                if (kronosPayCodeList[i]?.TimeOffReasonId != null)
                {
                    var timeOff = new TimeOff
                    {
                        UserId        = userModelNotFoundList[i].ShiftUserId,
                        SharedTimeOff = new TimeOffItem
                        {
                            TimeOffReasonId = kronosPayCodeList[i].TimeOffReasonId,
                            StartDateTime   = this.utility.CalculateStartDateTime(timeOffNotFoundList[i], userModelNotFoundList[i].KronosTimeZone),
                            EndDateTime     = this.CalculateEndDate(timeOffNotFoundList[i], userModelNotFoundList[i].KronosTimeZone),
                            Theme           = ScheduleEntityTheme.White,
                        },
                    };

                    var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI");

                    // Send Passthrough header to indicate the sender of request in outbound call.
                    httpClient.DefaultRequestHeaders.Add("X-MS-WFMPassthrough", configurationDetails.WFIId);

                    var requestUrl    = $"teams/{userModelNotFoundList[i].ShiftTeamId}/schedule/timesOff";
                    var requestString = JsonConvert.SerializeObject(timeOff);

                    var response = await this.graphUtility.SendHttpRequest(configurationDetails.GraphConfigurationDetails, httpClient, HttpMethod.Post, requestUrl, requestString).ConfigureAwait(false);

                    if (response.IsSuccessStatusCode)
                    {
                        var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                        var timeOffResponse = JsonConvert.DeserializeObject <TimeOff>(responseContent);

                        TimeOffMappingEntity timeOffMappingEntity = new TimeOffMappingEntity
                        {
                            Duration           = timeOffNotFoundList[i].TimeOffPeriods.TimeOffPeriod.Duration,
                            EndDate            = timeOffNotFoundList[i].TimeOffPeriods.TimeOffPeriod.EndDate,
                            StartDate          = timeOffNotFoundList[i].TimeOffPeriods.TimeOffPeriod.StartDate,
                            StartTime          = timeOffNotFoundList[i].TimeOffPeriods.TimeOffPeriod.StartTime,
                            PayCodeName        = timeOffNotFoundList[i].TimeOffPeriods.TimeOffPeriod.PayCodeName,
                            KronosPersonNumber = timeOffNotFoundList[i].Employee.PersonIdentity.PersonNumber,
                            PartitionKey       = monthPartitionKey,
                            RowKey             = timeOffNotFoundList[i].Id,
                            ShiftsRequestId    = timeOffResponse.Id,
                            KronosRequestId    = timeOffNotFoundList[i].Id,
                            ShiftsStatus       = ApiConstants.Pending,
                            KronosStatus       = ApiConstants.Submitted,
                            IsActive           = true,
                        };

                        this.AddorUpdateTimeOffMappingAsync(timeOffMappingEntity);
                    }
                }
                else
                {
                    telemetryProps.Add("TimeOffReason for " + timeOffNotFoundList[i].Id, "NotFound");
                }

                this.telemetryClient.TrackTrace($"AddTimeOffRequestAsync ended for {monthPartitionKey}.", telemetryProps);
            }
        }
        /// <summary>
        /// Creates and sends the relevant request to approve or deny a time off request.
        /// </summary>
        /// <param name="kronosReqId">The Kronos request id for the time off request.</param>
        /// <param name="kronosUserId">The Kronos user id for the assigned user.</param>
        /// <param name="teamsTimeOffEntity">The Teams time off entity.</param>
        /// <param name="timeOffRequestMapping">The mapping for the time off request.</param>
        /// <param name="managerMessage">The manager action message from Teams.</param>
        /// <param name="approved">Whether the request should be approved (true) or denied (false).</param>
        /// <param name="kronosTimeZone">The Kronos timezone.</param>
        /// <returns>Returns a bool that represents whether the request was a success (true) or not (false).</returns>
        internal async Task <bool> ApproveOrDenyTimeOffRequestInKronos(
            string kronosReqId,
            string kronosUserId,
            TimeOffRequestItem teamsTimeOffEntity,
            TimeOffMappingEntity timeOffRequestMapping,
            string managerMessage,
            bool approved,
            string kronosTimeZone)
        {
            var provider = CultureInfo.InvariantCulture;

            this.telemetryClient.TrackTrace($"{Resource.ProcessTimeOffRequestsAsync} start at: {DateTime.Now.ToString("o", provider)}");

            // Teams provides date times in UTC so convert to the local time.
            var localStartDateTime = this.utility.UTCToKronosTimeZone(teamsTimeOffEntity.StartDateTime, kronosTimeZone);
            var localEndDateTime   = this.utility.UTCToKronosTimeZone(teamsTimeOffEntity.EndDateTime, kronosTimeZone);

            var queryDateSpanStart = localStartDateTime.ToString(this.appSettings.KronosQueryDateSpanFormat, CultureInfo.InvariantCulture);
            var queryDateSpanEnd   = localEndDateTime.ToString(this.appSettings.KronosQueryDateSpanFormat, CultureInfo.InvariantCulture);

            var timeOffRequestQueryDateSpan = $"{queryDateSpanStart}-{queryDateSpanEnd}";

            // Get all the necessary prerequisites.
            var allRequiredConfigurations = await this.utility.GetAllConfigurationsAsync().ConfigureAwait(false);

            Dictionary <string, string> data = new Dictionary <string, string>
            {
                { "KronosPersonNumber", $"{kronosUserId}" },
                { "KronosTimeOffRequestId", $"{kronosReqId}" },
                { "Approved", $"{approved}" },
                { "Configured correctly", $"{allRequiredConfigurations.IsAllSetUpExists}" },
                { "Date range", $"{timeOffRequestQueryDateSpan}" },
            };

            if (allRequiredConfigurations.IsAllSetUpExists)
            {
                var commentTimeStamp = this.utility.UTCToKronosTimeZone(DateTime.UtcNow, kronosTimeZone).ToString(CultureInfo.InvariantCulture);
                var comments         = XmlHelper.GenerateKronosComments(managerMessage, this.appSettings.ManagerTimeOffRequestCommentText, commentTimeStamp);

                var response = await this.timeOffActivity.ApproveOrDenyTimeOffRequestAsync(
                    new Uri(allRequiredConfigurations.WfmEndPoint),
                    allRequiredConfigurations.KronosSession,
                    timeOffRequestQueryDateSpan,
                    kronosUserId,
                    approved,
                    kronosReqId,
                    comments).ConfigureAwait(false);

                data.Add("ResponseStatus", $"{response.Status}");

                if (response.Status == "Success" && approved)
                {
                    this.telemetryClient.TrackTrace($"Update table for approval of time off request: {kronosReqId}", data);
                    timeOffRequestMapping.KronosStatus = ApiConstants.ApprovedStatus;
                    await this.timeOffMappingEntityProvider.SaveOrUpdateTimeOffMappingEntityAsync(timeOffRequestMapping).ConfigureAwait(false);

                    return(true);
                }

                if (response.Status == "Success" && !approved)
                {
                    this.telemetryClient.TrackTrace($"Update table for refusal of time off request: {kronosReqId}", data);
                    timeOffRequestMapping.KronosStatus = ApiConstants.Refused;
                    await this.timeOffMappingEntityProvider.SaveOrUpdateTimeOffMappingEntityAsync(timeOffRequestMapping).ConfigureAwait(false);

                    return(true);
                }
            }

            this.telemetryClient.TrackTrace("ApproveOrDenyTimeOffRequestInKronos - Configuration incorrect", data);
            return(false);
        }
        /// <summary>
        /// Creates a time off request that was requested in Teams.
        /// </summary>
        /// <param name="user">The user details of the time off requestor.</param>
        /// <param name="timeOffEntity">The time off to be created.</param>
        /// <param name="timeOffReason">The time off reason.</param>
        /// <param name="allRequiredConfigurations">Setup details.</param>
        /// <param name="kronosTimeZone">The kronos timezone.</param>
        /// <returns>Whether the time off request was created successfully or not.</returns>
        internal async Task <bool> CreateTimeOffRequestInKronosAsync(
            UserDetailsModel user,
            TimeOffRequestItem timeOffEntity,
            PayCodeToTimeOffReasonsMappingEntity timeOffReason,
            SetupDetails allRequiredConfigurations,
            string kronosTimeZone)
        {
            // Teams provides date times in UTC so convert to the local time.
            var localStartDateTime = this.utility.UTCToKronosTimeZone(timeOffEntity.StartDateTime, kronosTimeZone);
            var localEndDateTime   = this.utility.UTCToKronosTimeZone(timeOffEntity.EndDateTime, kronosTimeZone);

            // Construct the query date span for the Kronos request
            var queryStartDate = localStartDateTime.AddDays(
                -Convert.ToInt16(this.appSettings.CorrectedDateSpanForOutboundCalls, CultureInfo.InvariantCulture))
                                 .ToString(this.appSettings.KronosQueryDateSpanFormat, CultureInfo.InvariantCulture);

            var queryEndDate = localEndDateTime.AddDays(
                Convert.ToInt16(this.appSettings.CorrectedDateSpanForOutboundCalls, CultureInfo.InvariantCulture))
                               .ToString(this.appSettings.KronosQueryDateSpanFormat, CultureInfo.InvariantCulture);

            var timeOffReqQueryDateSpan = $"{queryStartDate}-{queryEndDate}";

            var commentTimeStamp = this.utility.UTCToKronosTimeZone(DateTime.UtcNow, kronosTimeZone).ToString(CultureInfo.InvariantCulture);
            var comments         = XmlHelper.GenerateKronosComments(timeOffEntity.SenderMessage, this.appSettings.SenderTimeOffRequestCommentText, commentTimeStamp);

            // Create the Kronos Time Off Request.
            var timeOffResponse = await this.timeOffActivity.CreateTimeOffRequestAsync(
                allRequiredConfigurations.KronosSession,
                localStartDateTime,
                localEndDateTime,
                timeOffReqQueryDateSpan,
                user.KronosPersonNumber,
                timeOffReason.RowKey,
                comments,
                new Uri(allRequiredConfigurations.WfmEndPoint)).ConfigureAwait(false);

            if (!string.IsNullOrWhiteSpace(timeOffResponse?.Error?.Message))
            {
                this.telemetryClient.TrackTrace($"Could not create the time off request : {timeOffResponse?.Error?.Message} ");
                return(false);
            }

            var submitTimeOffResponse = await this.timeOffActivity.SubmitTimeOffRequestAsync(
                allRequiredConfigurations.KronosSession,
                user.KronosPersonNumber,
                timeOffResponse?.EmployeeRequestMgm?.RequestItem?.GlobalTimeOffRequestItms?.FirstOrDefault()?.Id,
                timeOffReqQueryDateSpan,
                new Uri(allRequiredConfigurations.WfmEndPoint)).ConfigureAwait(false);

            TimeOffMappingEntity newTimeOffReq = new TimeOffMappingEntity();

            // IsActive represents whether the time off was successfully created.
            if (submitTimeOffResponse?.Status == ApiConstants.Failure)
            {
                newTimeOffReq.IsActive = false;
            }
            else
            {
                newTimeOffReq.IsActive = true;
            }

            newTimeOffReq.Duration           = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().TimeOffPeriodsList.TimeOffPerd.FirstOrDefault().Duration;
            newTimeOffReq.EndDate            = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().TimeOffPeriodsList.TimeOffPerd.FirstOrDefault().EndDate;
            newTimeOffReq.StartDate          = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().TimeOffPeriodsList.TimeOffPerd.FirstOrDefault().StartDate;
            newTimeOffReq.StartTime          = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().TimeOffPeriodsList.TimeOffPerd.FirstOrDefault().StartTime;
            newTimeOffReq.PayCodeName        = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().TimeOffPeriodsList.TimeOffPerd.FirstOrDefault().PayCodeName;
            newTimeOffReq.KronosPersonNumber = timeOffResponse.EmployeeRequestMgm.Employees.PersonIdentity.PersonNumber;
            newTimeOffReq.PartitionKey       = $"{localStartDateTime.Month}_{localStartDateTime.Year}";
            newTimeOffReq.RowKey             = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().Id;
            newTimeOffReq.ShiftsRequestId    = timeOffEntity.Id;
            newTimeOffReq.KronosRequestId    = timeOffResponse.EmployeeRequestMgm.RequestItem.GlobalTimeOffRequestItms.FirstOrDefault().Id;
            newTimeOffReq.KronosStatus       = ApiConstants.Submitted;
            newTimeOffReq.ShiftsStatus       = ApiConstants.Pending;

            this.AddorUpdateTimeOffMappingAsync(newTimeOffReq);

            // If isActive is false time off request was not submitted so return false and vice versa.
            return(newTimeOffReq.IsActive);
        }