/// <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); }
/// <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}."); }
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)); }
/// <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)); }
/// <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); }
/// <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}."); }
/// <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); } }
/// <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); }